From 5cd1ff203507ce539d3a601fd3a3d00055de3b5d Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Wed, 15 Dec 2021 13:31:25 -0500 Subject: [PATCH 01/12] fix(datetime): prevent navigating to disabled months --- core/src/components/datetime/datetime.scss | 7 +++ core/src/components/datetime/datetime.tsx | 65 ++++++++++++++++++++-- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/core/src/components/datetime/datetime.scss b/core/src/components/datetime/datetime.scss index 974d576b145..eae5ea5b553 100644 --- a/core/src/components/datetime/datetime.scss +++ b/core/src/components/datetime/datetime.scss @@ -216,6 +216,13 @@ width: 100%; } +:host .calendar-body .calendar-month-disabled { + /** + * Disables swipe gestures for scroll-snap containers + */ + display: none; +} + /** * Hide scrollbars on Chrome and Safari */ diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 0865b9546c5..9a8e85093ce 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -936,9 +936,40 @@ export class Datetime implements ComponentInterface { }); } + private getDefaultDateParts = (value?: string | null) => { + let dateParts; + + if (typeof value !== 'undefined') { + dateParts = parseDate(value); + } else { + const todayParts: DatetimeParts = parseDate(getToday()); + dateParts = todayParts; + + if (todayParts.month < this.minParts?.month && todayParts.year <= this.minParts?.year || todayParts.month > this.maxParts?.month && todayParts.year >= this.maxParts?.year) { + /** + * Today's date is either below the minimum date range or exceeds the maximum + * date range. + * + * Take the minimum date value to default the user at the lower bounds + * of the date range. + */ + dateParts = { + month: Math.min(this.minParts?.month, this.maxParts?.month), + year: Math.min(this.minParts?.year, this.maxParts?.year), + day: Math.min(this.minParts?.day, this.maxParts?.day) || 1, + dayOfWeek: Math.min(this.minParts?.dayOfWeek, this.maxParts?.dayOfWeek) || 1, + hour: this.minParts?.hour ?? todayParts.hour, + minute: this.minParts?.minute ?? todayParts.minute, + ampm: this.minParts?.ampm ?? todayParts.ampm, + tzOffset: todayParts.tzOffset, + } + } + } + return dateParts; + } + private processValue = (value?: string | null) => { - const valueToProcess = value || getToday(); - const { month, day, year, hour, minute, tzOffset } = parseDate(valueToProcess); + const { month, day, year, hour, minute, tzOffset } = this.getDefaultDateParts(value); this.workingParts = { month, @@ -963,9 +994,9 @@ export class Datetime implements ComponentInterface { } componentWillLoad() { - this.processValue(this.value); this.processMinParts(); this.processMaxParts(); + this.processValue(this.value); this.parsedHourValues = convertToArrayOfNumbers(this.hourValues); this.parsedMinuteValues = convertToArrayOfNumbers(this.minuteValues); this.parsedMonthValues = convertToArrayOfNumbers(this.monthValues); @@ -994,6 +1025,14 @@ export class Datetime implements ComponentInterface { return this.value != null && this.value !== ''; } + private isPrevMonthDisabled = () => { + return this.workingParts.month <= this.minParts?.month && this.workingParts.year <= this.minParts?.year; + } + + private isNextMonthDisabled = () => { + return this.workingParts.month >= this.maxParts?.month && this.workingParts.year >= this.maxParts?.year; + } + private nextMonth = () => { const { calendarBodyRef } = this; if (!calendarBodyRef) { return; } @@ -1139,6 +1178,10 @@ export class Datetime implements ComponentInterface { private renderCalendarHeader(mode: Mode) { const expandedIcon = mode === 'ios' ? chevronDown : caretUpSharp; const collapsedIcon = mode === 'ios' ? chevronForward : caretDownSharp; + + const prevMonthDisabled = this.isPrevMonthDisabled(); + const nextMonthDisabled = this.isNextMonthDisabled(); + return (
@@ -1152,10 +1195,14 @@ export class Datetime implements ComponentInterface {
- this.prevMonth()}> + this.prevMonth()}> - this.nextMonth()}> + this.nextMonth()}> @@ -1174,8 +1221,14 @@ export class Datetime implements ComponentInterface { const yearAllowed = this.parsedYearValues === undefined || this.parsedYearValues.includes(year); const monthAllowed = this.parsedMonthValues === undefined || this.parsedMonthValues.includes(month); const isMonthDisabled = !yearAllowed || !monthAllowed; + const monthDisabled = month < this.minParts?.month && year <= this.minParts?.year || month > this.maxParts?.month && year >= this.maxParts?.year; + return ( -
+
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => { const { day, dayOfWeek } = dateObject; From 2af54e784cfc4f318d9b0c0cc7958d5764177707 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Wed, 15 Dec 2021 15:55:52 -0500 Subject: [PATCH 02/12] test(datetime): minmax tests --- .../components/datetime/test/minmax/e2e.ts | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/core/src/components/datetime/test/minmax/e2e.ts b/core/src/components/datetime/test/minmax/e2e.ts index d37ff377610..6be6a7251be 100644 --- a/core/src/components/datetime/test/minmax/e2e.ts +++ b/core/src/components/datetime/test/minmax/e2e.ts @@ -1,6 +1,7 @@ import { newE2EPage } from '@stencil/core/testing'; +import { expectFiles } from '@stencil/core/testing/testing-utils'; -test('minmax', async () => { +test('datetime: minmax', async () => { const page = await newE2EPage({ url: '/src/components/datetime/test/minmax?ionic:_testing=true' }); @@ -20,3 +21,30 @@ test('minmax', async () => { expect(screenshotCompare).toMatchScreenshot(); } }); + +test('datetime: minmax months disabled', async () => { + const page = await newE2EPage({ + url: '/src/components/datetime/test/minmax?ionic:_testing=true' + }); + + const calendarMonths = await page.findAll('ion-datetime#inside >>> .calendar-month'); + + await page.waitForChanges(); + + expect(calendarMonths[0]).toHaveClass('calendar-month-disabled'); + expect(calendarMonths[1]).not.toHaveClass('calendar-month-disabled'); + expect(calendarMonths[2]).not.toHaveClass('calendar-month-disabled'); + +}); + +test('datetime: minmax navigation disabled', async () => { + const page = await newE2EPage({ + url: '/src/components/datetime/test/minmax?ionic:_testing=true' + }); + + const navButtons = await page.findAll('ion-datetime#outside >>> .calendar-next-prev ion-button'); + + expect(navButtons[0]).toHaveAttribute('disabled'); + expect(navButtons[1]).toHaveAttribute('disabled'); + +}); From 58f24ff9ad63614f7c5f3db0bd4aed0d4aaa3fe1 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Thu, 16 Dec 2021 15:20:50 -0500 Subject: [PATCH 03/12] fix(): split prev month/next month utils into state utils --- core/src/components/datetime/datetime.tsx | 59 ++------- core/src/components/datetime/utils/state.ts | 129 ++++++++++++++++++++ 2 files changed, 141 insertions(+), 47 deletions(-) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 9a8e85093ce..ff1fa7a329c 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -56,7 +56,10 @@ import { } from './utils/parse'; import { getCalendarDayState, - isDayDisabled + isDayDisabled, + isMonthSwipeDisabled, + isNextMonthDisabled, + isPrevMonthDisabled } from './utils/state'; /** @@ -724,7 +727,8 @@ export class Datetime implements ComponentInterface { */ if (mode === 'ios') { const ratio = ev.intersectionRatio; - const shouldDisable = Math.abs(ratio - 0.7) <= 0.1; + // `maxTouchPoints` will be 1 in device preview, but > 1 on device + const shouldDisable = Math.abs(ratio - 0.7) <= 0.1 && navigator.maxTouchPoints > 1; if (shouldDisable) { calendarBodyRef.style.setProperty('pointer-events', 'none'); @@ -936,40 +940,9 @@ export class Datetime implements ComponentInterface { }); } - private getDefaultDateParts = (value?: string | null) => { - let dateParts; - - if (typeof value !== 'undefined') { - dateParts = parseDate(value); - } else { - const todayParts: DatetimeParts = parseDate(getToday()); - dateParts = todayParts; - - if (todayParts.month < this.minParts?.month && todayParts.year <= this.minParts?.year || todayParts.month > this.maxParts?.month && todayParts.year >= this.maxParts?.year) { - /** - * Today's date is either below the minimum date range or exceeds the maximum - * date range. - * - * Take the minimum date value to default the user at the lower bounds - * of the date range. - */ - dateParts = { - month: Math.min(this.minParts?.month, this.maxParts?.month), - year: Math.min(this.minParts?.year, this.maxParts?.year), - day: Math.min(this.minParts?.day, this.maxParts?.day) || 1, - dayOfWeek: Math.min(this.minParts?.dayOfWeek, this.maxParts?.dayOfWeek) || 1, - hour: this.minParts?.hour ?? todayParts.hour, - minute: this.minParts?.minute ?? todayParts.minute, - ampm: this.minParts?.ampm ?? todayParts.ampm, - tzOffset: todayParts.tzOffset, - } - } - } - return dateParts; - } - private processValue = (value?: string | null) => { - const { month, day, year, hour, minute, tzOffset } = this.getDefaultDateParts(value); + const valueToProcess = value || getToday(); + const { month, day, year, hour, minute, tzOffset } = parseDate(valueToProcess); this.workingParts = { month, @@ -1025,14 +998,6 @@ export class Datetime implements ComponentInterface { return this.value != null && this.value !== ''; } - private isPrevMonthDisabled = () => { - return this.workingParts.month <= this.minParts?.month && this.workingParts.year <= this.minParts?.year; - } - - private isNextMonthDisabled = () => { - return this.workingParts.month >= this.maxParts?.month && this.workingParts.year >= this.maxParts?.year; - } - private nextMonth = () => { const { calendarBodyRef } = this; if (!calendarBodyRef) { return; } @@ -1179,8 +1144,8 @@ export class Datetime implements ComponentInterface { const expandedIcon = mode === 'ios' ? chevronDown : caretUpSharp; const collapsedIcon = mode === 'ios' ? chevronForward : caretDownSharp; - const prevMonthDisabled = this.isPrevMonthDisabled(); - const nextMonthDisabled = this.isNextMonthDisabled(); + const prevMonthDisabled = isPrevMonthDisabled(this.workingParts, this.minParts, this.maxParts); + const nextMonthDisabled = isNextMonthDisabled(this.workingParts, this.maxParts); return (
@@ -1221,13 +1186,13 @@ export class Datetime implements ComponentInterface { const yearAllowed = this.parsedYearValues === undefined || this.parsedYearValues.includes(year); const monthAllowed = this.parsedMonthValues === undefined || this.parsedMonthValues.includes(month); const isMonthDisabled = !yearAllowed || !monthAllowed; - const monthDisabled = month < this.minParts?.month && year <= this.minParts?.year || month > this.maxParts?.month && year >= this.maxParts?.year; + const swipeDisabled = isMonthSwipeDisabled(month, year, this.workingParts, this.minParts, this.maxParts); return (
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => { diff --git a/core/src/components/datetime/utils/state.ts b/core/src/components/datetime/utils/state.ts index 4e279a94072..fa43e6e6ecc 100644 --- a/core/src/components/datetime/utils/state.ts +++ b/core/src/components/datetime/utils/state.ts @@ -15,6 +15,40 @@ export const isYearDisabled = (refYear: number, minParts?: DatetimeParts, maxPar return false; } +export const isMonthSwipeDisabled = (refMonth: number, refYear: number, workingParts?: DatetimeParts, minParts?: DatetimeParts, maxParts?: DatetimeParts) => { + if (workingParts && refMonth === workingParts.month && refYear === workingParts.year) { + /** + * Do not disable the working month, if it's the current month being rendered. + * + * If the working month is disabled, scroll snap will not clip to the month + * and will scroll the user to the first "enabled" calendar month. + */ + return false; + } + + if (minParts) { + if (minParts.month) { + // If the minimum parts contains a month, compare both the month and year + if (refMonth < minParts.month && refYear <= minParts.year) { + return true; + } + // Otherwise only compare the year range + } else if (minParts.year && refYear < minParts.year) { + return true; + } + } + + if (maxParts) { + if (maxParts.month && refMonth > maxParts.month && refYear >= maxParts.year) { + return true; + } else if (maxParts.year && refYear > maxParts.year) { + return true; + } + } + + return false; +} + /** * Returns true if a given day should * not be interactive according to its value, @@ -102,3 +136,98 @@ export const getCalendarDayState = ( ariaLabel: generateDayAriaLabel(locale, isToday, refParts) } } + +/** + * Given a working date and a minimum date range, + * determine if the previous navigation button is disabled. + */ +export const isPrevMonthDisabled = (refParts: { + month: number, + year: number +}, minParts?: { + month?: number, + year: number +}, maxParts?: { + month?: number, + year: number +}) => { + if (minParts) { + if (minParts.month) { + /** + * Disables the previous month is the current date is either at the minimum + * month or before the minimum range. + * + * i.e.: + * - Ref: 09/2021 + * - Min: 10/2021 + */ + if (refParts.month <= minParts.month && refParts.year <= minParts.year) { + return true; + } + } else if (minParts.year) { + /** + * The minimum range only includes a year. Compare that the current date's year + * is either at the minimum year or before the minimum year. + * + * i.e: + * - Ref: 2021 + * - Min: 2021 + */ + if (refParts.month === 1) { + if (refParts.year <= minParts.year) { + return true; + } + } else if (refParts.year < minParts.year) { + return true; + } + } + } + if (maxParts) { + if (maxParts.month) { + /** + * In situations where the current date is outside the bounds of the upper (max) range, + * we need to check if the previous month would return the date range into the valid range. + * + * i.e.: + * - Date: 12/16/2021 + * - Max: 10/2021 + * - Min: 09/2021 + * + * If it does, we allow the previous button to navigate back one step, otherwise we lock + * navigation and require they use the month/year selector. + */ + if (refParts.month - 1 === 0) { + // The reference month is in January, so we need to step back to December of the previous year. + if (12 > maxParts.month && refParts.year - 1 === maxParts.year) { + return true; + } + } else if (refParts.month - 1 > maxParts.month && refParts.year === maxParts.year) { + // Otherwise we are comparing if the previous month in the same year is within + // the maximum date range. + return true; + } + } + } + return false; +} + +/** + * Given a working date and a maximum date range, + * determine if the next navigation button is disabled. + */ +export const isNextMonthDisabled = (refParts: { + month: number, + year: number +}, maxParts?: { + month?: number, + year: number +}) => { + if (maxParts) { + if (maxParts.month && refParts.month >= maxParts.month && refParts.year >= maxParts.year) { + return true; + } else if (maxParts.year && refParts.year > maxParts.year) { + return true; + } + } + return false; +} From 1910f5bd84d5dbfdc6f3c9c28ed29f64cb89c7ba Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Thu, 16 Dec 2021 15:21:10 -0500 Subject: [PATCH 04/12] test(datetime): add prev/next disable state tests --- .../components/datetime/test/minmax/e2e.ts | 1 - .../components/datetime/test/state.spec.ts | 59 ++++++++++++++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/core/src/components/datetime/test/minmax/e2e.ts b/core/src/components/datetime/test/minmax/e2e.ts index 6be6a7251be..9888f9c9d09 100644 --- a/core/src/components/datetime/test/minmax/e2e.ts +++ b/core/src/components/datetime/test/minmax/e2e.ts @@ -1,5 +1,4 @@ import { newE2EPage } from '@stencil/core/testing'; -import { expectFiles } from '@stencil/core/testing/testing-utils'; test('datetime: minmax', async () => { const page = await newE2EPage({ diff --git a/core/src/components/datetime/test/state.spec.ts b/core/src/components/datetime/test/state.spec.ts index 1bcc0eb5d02..13e887e8716 100644 --- a/core/src/components/datetime/test/state.spec.ts +++ b/core/src/components/datetime/test/state.spec.ts @@ -1,6 +1,8 @@ import { getCalendarDayState, - isDayDisabled + isDayDisabled, + isNextMonthDisabled, + isPrevMonthDisabled } from '../utils/state'; describe('getCalendarDayState()', () => { @@ -73,3 +75,58 @@ describe('isDayDisabled()', () => { expect(isDayDisabled(refDate, undefined, { month: 5, day: 11, year: 2021 })).toEqual(true); }) }); + +describe('isPrevMonthDisabled()', () => { + + it('should return true', () => { + // Date month is before min month, in the same year + expect(isPrevMonthDisabled({ month: 5, year: 2021 }, { month: 6, year: 2021 })).toEqual(true); + // Date month and year is the same as min month and year + expect(isPrevMonthDisabled({ month: 1, year: 2021 }, { month: 1, year: 2021 })).toEqual(true); + // Date year is the same as min year (month not provided) + expect(isPrevMonthDisabled({ month: 1, year: 2021 }, { year: 2021 })).toEqual(true); + // Date year is less than the min year (month not provided) + expect(isPrevMonthDisabled({ month: 5, year: 2021 }, { year: 2022 })).toEqual(true); + + // Date is above the maximum bounds and the previous month does not does not fall within the + // min-max range. + expect(isPrevMonthDisabled({ month: 12, year: 2021 }, { month: 9, year: 2021 }, { month: 10, year: 2021 })).toEqual(true); + + // Date is above the maximum bounds and a year ahead of the max range. The previous month/year + // does not fall within the min-max range. + expect(isPrevMonthDisabled({ month: 1, year: 2022 }, { month: 9, year: 2021 }, { month: 10, year: 2021 })).toEqual(true); + + }); + + it('should return false', () => { + // No min range provided + expect(isPrevMonthDisabled({ month: 12, year: 2021 })).toEqual(false); + // Date year is the same as min year, + // but can navigate to a previous month without reducing the year. + expect(isPrevMonthDisabled({ month: 12, year: 2021 }, { year: 2021 })).toEqual(false); + expect(isPrevMonthDisabled({ month: 2, year: 2021 }, { year: 2021 })).toEqual(false); + }); + +}); + +describe('isNextMonthDisabled()', () => { + + it('should return true', () => { + // Date month is the same as max month (in the same year) + expect(isNextMonthDisabled({ month: 10, year: 2021 }, { month: 10, year: 2021 })).toEqual(true); + // Date month is after the max month (in the same year) + expect(isNextMonthDisabled({ month: 10, year: 2021 }, { month: 9, year: 2021 })).toEqual(true); + // Date year is after the max month and year + expect(isNextMonthDisabled({ month: 10, year: 2022 }, { month: 12, year: 2021 })).toEqual(true); + }); + + it('should return false', () => { + // No max range provided + expect(isNextMonthDisabled({ month: 10, year: 2021 })).toBe(false); + // Date month is before max month and is the previous month, + // so that navigating the next month would re-enter the max range + expect(isNextMonthDisabled({ month: 10, year: 2021 }, { month: 11, year: 2021 })).toEqual(false); + }); + +}); + From b58954209d26caf37819e901c9dd54a347e1a222 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Thu, 16 Dec 2021 15:29:30 -0500 Subject: [PATCH 05/12] chore(): code documentation typos --- core/src/components/datetime/utils/state.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/components/datetime/utils/state.ts b/core/src/components/datetime/utils/state.ts index fa43e6e6ecc..34458746ebb 100644 --- a/core/src/components/datetime/utils/state.ts +++ b/core/src/components/datetime/utils/state.ts @@ -138,8 +138,9 @@ export const getCalendarDayState = ( } /** - * Given a working date and a minimum date range, - * determine if the previous navigation button is disabled. + * Given a working date, an optional minimum date range, + * and an optional maximum date range; determine if the + * previous navigation button is disabled. */ export const isPrevMonthDisabled = (refParts: { month: number, @@ -154,7 +155,7 @@ export const isPrevMonthDisabled = (refParts: { if (minParts) { if (minParts.month) { /** - * Disables the previous month is the current date is either at the minimum + * Disables the previous month if the current date is either at the minimum * month or before the minimum range. * * i.e.: From 75501d0bbfa1fe117bd9337c4d87cba1a7d47281 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Fri, 17 Dec 2021 15:34:50 -0500 Subject: [PATCH 06/12] chore(): lint issues --- core/src/components/datetime/utils/state.ts | 91 +++++++++++---------- 1 file changed, 50 insertions(+), 41 deletions(-) diff --git a/core/src/components/datetime/utils/state.ts b/core/src/components/datetime/utils/state.ts index 34458746ebb..b1c72ed7c57 100644 --- a/core/src/components/datetime/utils/state.ts +++ b/core/src/components/datetime/utils/state.ts @@ -15,7 +15,12 @@ export const isYearDisabled = (refYear: number, minParts?: DatetimeParts, maxPar return false; } -export const isMonthSwipeDisabled = (refMonth: number, refYear: number, workingParts?: DatetimeParts, minParts?: DatetimeParts, maxParts?: DatetimeParts) => { +export const isMonthSwipeDisabled = ( + refMonth: number, + refYear: number, + workingParts?: DatetimeParts, + minParts?: { month?: number, year: number }, + maxParts?: { month?: number, year: number }) => { if (workingParts && refMonth === workingParts.month && refYear === workingParts.year) { /** * Do not disable the working month, if it's the current month being rendered. @@ -27,21 +32,21 @@ export const isMonthSwipeDisabled = (refMonth: number, refYear: number, workingP } if (minParts) { - if (minParts.month) { + if (typeof minParts.month !== 'undefined') { // If the minimum parts contains a month, compare both the month and year if (refMonth < minParts.month && refYear <= minParts.year) { return true; } // Otherwise only compare the year range - } else if (minParts.year && refYear < minParts.year) { + } else if (refYear < minParts.year) { return true; } } if (maxParts) { - if (maxParts.month && refMonth > maxParts.month && refYear >= maxParts.year) { + if (typeof maxParts.month !== 'undefined' && refMonth > maxParts.month && refYear >= maxParts.year) { return true; - } else if (maxParts.year && refYear > maxParts.year) { + } else if (refYear > maxParts.year) { return true; } } @@ -142,18 +147,21 @@ export const getCalendarDayState = ( * and an optional maximum date range; determine if the * previous navigation button is disabled. */ -export const isPrevMonthDisabled = (refParts: { - month: number, - year: number -}, minParts?: { - month?: number, - year: number -}, maxParts?: { - month?: number, - year: number -}) => { +export const isPrevMonthDisabled = ( + refParts: { + month: number, + year: number + }, + minParts?: { + month?: number, + year: number + }, + maxParts?: { + month?: number, + year: number + }) => { if (minParts) { - if (minParts.month) { + if (typeof minParts.month !== 'undefined') { /** * Disables the previous month if the current date is either at the minimum * month or before the minimum range. @@ -165,32 +173,31 @@ export const isPrevMonthDisabled = (refParts: { if (refParts.month <= minParts.month && refParts.year <= minParts.year) { return true; } - } else if (minParts.year) { - /** - * The minimum range only includes a year. Compare that the current date's year - * is either at the minimum year or before the minimum year. - * - * i.e: - * - Ref: 2021 - * - Min: 2021 - */ - if (refParts.month === 1) { - if (refParts.year <= minParts.year) { - return true; - } - } else if (refParts.year < minParts.year) { + } + /** + * The minimum range only includes a year. Compare that the current date's year + * is either at the minimum year or before the minimum year. + * + * i.e: + * - Ref: 2021 + * - Min: 2021 + */ + if (refParts.month === 1) { + if (refParts.year <= minParts.year) { return true; } + } else if (refParts.year < minParts.year) { + return true; } } if (maxParts) { - if (maxParts.month) { + if (typeof maxParts.month !== 'undefined') { /** * In situations where the current date is outside the bounds of the upper (max) range, * we need to check if the previous month would return the date range into the valid range. * * i.e.: - * - Date: 12/16/2021 + * - Date: 11/16/2021 * - Max: 10/2021 * - Min: 09/2021 * @@ -216,17 +223,19 @@ export const isPrevMonthDisabled = (refParts: { * Given a working date and a maximum date range, * determine if the next navigation button is disabled. */ -export const isNextMonthDisabled = (refParts: { - month: number, - year: number -}, maxParts?: { - month?: number, - year: number -}) => { +export const isNextMonthDisabled = ( + refParts: { + month: number, + year: number + }, + maxParts?: { + month?: number, + year: number + }) => { if (maxParts) { - if (maxParts.month && refParts.month >= maxParts.month && refParts.year >= maxParts.year) { + if (typeof maxParts.month !== 'undefined' && refParts.month >= maxParts.month && refParts.year >= maxParts.year) { return true; - } else if (maxParts.year && refParts.year > maxParts.year) { + } else if (refParts.year > maxParts.year) { return true; } } From 4733bf6b12e2dfa50e5b0b012939d382f34f7481 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Wed, 26 Jan 2022 14:57:51 -0500 Subject: [PATCH 07/12] fix(): datetime scroll snap behavior with webkit --- core/src/components/datetime/datetime.scss | 4 ++-- core/src/components/datetime/datetime.tsx | 15 +++++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/core/src/components/datetime/datetime.scss b/core/src/components/datetime/datetime.scss index 694038defdb..ce8438b6db1 100644 --- a/core/src/components/datetime/datetime.scss +++ b/core/src/components/datetime/datetime.scss @@ -243,9 +243,9 @@ :host .calendar-body .calendar-month-disabled { /** - * Disables swipe gestures for scroll-snap containers + * Disables swipe gesture snapping for scroll-snap containers */ - display: none; + scroll-snap-align: none; } /** diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index e67aeb24133..9c5a1c116f6 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -717,6 +717,12 @@ export class Datetime implements ComponentInterface { return; } + const { month, year, day } = refMonthFn(this.workingParts); + + if (isMonthSwipeDisabled(month, year, this.workingParts, this.minParts, this.maxParts)) { + return; + } + /** * On iOS, we need to set pointer-events: none * when the user is almost done with the gesture @@ -761,7 +767,6 @@ export class Datetime implements ComponentInterface { * if we did not do this. */ writeTask(() => { - const { month, year, day } = refMonthFn(this.workingParts); this.setWorkingParts({ ...this.workingParts, @@ -770,9 +775,11 @@ export class Datetime implements ComponentInterface { year }); - calendarBodyRef.scrollLeft = workingMonth.clientWidth * (isRTL(this.el) ? -1 : 1); - calendarBodyRef.style.removeProperty('overflow'); - calendarBodyRef.style.removeProperty('pointer-events'); + raf(() => { + calendarBodyRef.scrollLeft = workingMonth.clientWidth * (isRTL(this.el) ? -1 : 1); + calendarBodyRef.style.removeProperty('overflow'); + calendarBodyRef.style.removeProperty('pointer-events'); + }); /** * Now that state has been updated From 4ea28d0bcec43e99e61e24dfe9cc87389063dc7c Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Wed, 26 Jan 2022 15:43:19 -0500 Subject: [PATCH 08/12] fix(): test within bounds --- core/src/components/datetime/test/minmax/e2e.ts | 4 ++-- core/src/components/datetime/test/minmax/index.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/components/datetime/test/minmax/e2e.ts b/core/src/components/datetime/test/minmax/e2e.ts index 9888f9c9d09..f040b65d66d 100644 --- a/core/src/components/datetime/test/minmax/e2e.ts +++ b/core/src/components/datetime/test/minmax/e2e.ts @@ -30,9 +30,9 @@ test('datetime: minmax months disabled', async () => { await page.waitForChanges(); - expect(calendarMonths[0]).toHaveClass('calendar-month-disabled'); + expect(calendarMonths[0]).not.toHaveClass('calendar-month-disabled'); expect(calendarMonths[1]).not.toHaveClass('calendar-month-disabled'); - expect(calendarMonths[2]).not.toHaveClass('calendar-month-disabled'); + expect(calendarMonths[2]).toHaveClass('calendar-month-disabled'); }); diff --git a/core/src/components/datetime/test/minmax/index.html b/core/src/components/datetime/test/minmax/index.html index fbba64a03aa..1f66778de15 100644 --- a/core/src/components/datetime/test/minmax/index.html +++ b/core/src/components/datetime/test/minmax/index.html @@ -44,7 +44,7 @@

Value inside Bounds

- +

Value Outside Bounds

From 4419ee0066155e1c3b9d76296976d8fdd588c94c Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Thu, 27 Jan 2022 13:20:48 -0500 Subject: [PATCH 09/12] fix(): Safari 14 re-render when selecting month or year --- core/src/components/datetime/datetime.tsx | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 9c5a1c116f6..197386a6ecd 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -1102,6 +1102,13 @@ export class Datetime implements ComponentInterface { items={months} value={workingParts.month} onIonChange={(ev: CustomEvent) => { + // Due to a Safari 14 issue we need to destroy + // the intersection observer before we update state + // and trigger a re-render. + if (this.destroyCalendarIO) { + this.destroyCalendarIO(); + } + this.setWorkingParts({ ...this.workingParts, month: ev.detail.value @@ -1114,6 +1121,10 @@ export class Datetime implements ComponentInterface { }); } + // We can re-attach the intersection observer after + // the working parts have been updated. + this.initializeCalendarIOListeners(); + ev.stopPropagation(); }} > @@ -1125,6 +1136,13 @@ export class Datetime implements ComponentInterface { items={years} value={workingParts.year} onIonChange={(ev: CustomEvent) => { + // Due to a Safari 14 issue we need to destroy + // the intersection observer before we update state + // and trigger a re-render. + if (this.destroyCalendarIO) { + this.destroyCalendarIO(); + } + this.setWorkingParts({ ...this.workingParts, year: ev.detail.value @@ -1137,6 +1155,10 @@ export class Datetime implements ComponentInterface { }); } + // We can re-attach the intersection observer after + // the working parts have been updated. + this.initializeCalendarIOListeners(); + ev.stopPropagation(); }} > From 443429992034c557dfb82243f3ee1d5115a20873 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Mon, 31 Jan 2022 12:20:11 -0500 Subject: [PATCH 10/12] chore(): isMonthDisabled util --- .../components/datetime/test/state.spec.ts | 18 +-- core/src/components/datetime/utils/state.ts | 113 +++++++----------- 2 files changed, 55 insertions(+), 76 deletions(-) diff --git a/core/src/components/datetime/test/state.spec.ts b/core/src/components/datetime/test/state.spec.ts index 13e887e8716..64790796e7c 100644 --- a/core/src/components/datetime/test/state.spec.ts +++ b/core/src/components/datetime/test/state.spec.ts @@ -80,31 +80,31 @@ describe('isPrevMonthDisabled()', () => { it('should return true', () => { // Date month is before min month, in the same year - expect(isPrevMonthDisabled({ month: 5, year: 2021 }, { month: 6, year: 2021 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 5, year: 2021, day: null }, { month: 6, year: 2021 })).toEqual(true); // Date month and year is the same as min month and year - expect(isPrevMonthDisabled({ month: 1, year: 2021 }, { month: 1, year: 2021 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { month: 1, year: 2021 })).toEqual(true); // Date year is the same as min year (month not provided) - expect(isPrevMonthDisabled({ month: 1, year: 2021 }, { year: 2021 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { year: 2021 })).toEqual(true); // Date year is less than the min year (month not provided) - expect(isPrevMonthDisabled({ month: 5, year: 2021 }, { year: 2022 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 5, year: 2021, day: null }, { year: 2022 })).toEqual(true); // Date is above the maximum bounds and the previous month does not does not fall within the // min-max range. - expect(isPrevMonthDisabled({ month: 12, year: 2021 }, { month: 9, year: 2021 }, { month: 10, year: 2021 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null }, { month: 9, year: 2021 }, { month: 10, year: 2021 })).toEqual(true); // Date is above the maximum bounds and a year ahead of the max range. The previous month/year // does not fall within the min-max range. - expect(isPrevMonthDisabled({ month: 1, year: 2022 }, { month: 9, year: 2021 }, { month: 10, year: 2021 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 1, year: 2022, day: null }, { month: 9, year: 2021 }, { month: 10, year: 2021 })).toEqual(true); }); it('should return false', () => { // No min range provided - expect(isPrevMonthDisabled({ month: 12, year: 2021 })).toEqual(false); + expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null })).toEqual(false); // Date year is the same as min year, // but can navigate to a previous month without reducing the year. - expect(isPrevMonthDisabled({ month: 12, year: 2021 }, { year: 2021 })).toEqual(false); - expect(isPrevMonthDisabled({ month: 2, year: 2021 }, { year: 2021 })).toEqual(false); + expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null }, { year: 2021 })).toEqual(false); + expect(isPrevMonthDisabled({ month: 2, year: 2021, day: null }, { year: 2021 })).toEqual(false); }); }); diff --git a/core/src/components/datetime/utils/state.ts b/core/src/components/datetime/utils/state.ts index b1c72ed7c57..134f6a80b28 100644 --- a/core/src/components/datetime/utils/state.ts +++ b/core/src/components/datetime/utils/state.ts @@ -2,6 +2,7 @@ import { DatetimeParts } from '../datetime-interface'; import { isAfter, isBefore, isSameDay } from './comparison'; import { generateDayAriaLabel } from './format'; +import { getNextMonth, getPreviousMonth } from './manipulation'; export const isYearDisabled = (refYear: number, minParts?: DatetimeParts, maxParts?: DatetimeParts) => { if (minParts && minParts.year > refYear) { @@ -142,6 +143,38 @@ export const getCalendarDayState = ( } } +/** + * Returns `true` if the month is disabled given the + * current date value and min/max date constraints. + */ +export const isMonthDisabled = (refParts: { month: number, year: number }, { minParts, maxParts }: { + minParts?: { month?: number, year: number }, + maxParts?: { + month?: number, year: number + } +}) => { + // If the min date is set and the year is less than the min year. + if (minParts && minParts.year > refParts.year) { + return true; + } + // If the max date is set and the year is greater than the max year. + if (maxParts && maxParts.year < refParts.year) { + return true; + } + // If the min date is set and the year is the same as the min year, + // but the month is less than the min month. + if (minParts && minParts.year === refParts.year && minParts.month !== undefined && minParts.month > refParts.month) { + return true; + } + // If the max date is set and the year is the same as the max year, + // but the month is greater than the max month. + if (maxParts && maxParts.year === refParts.year && maxParts.month !== undefined && maxParts.month < refParts.month) { + return true; + } + + return false; +} + /** * Given a working date, an optional minimum date range, * and an optional maximum date range; determine if the @@ -150,7 +183,8 @@ export const getCalendarDayState = ( export const isPrevMonthDisabled = ( refParts: { month: number, - year: number + year: number, + day: number | null }, minParts?: { month?: number, @@ -160,63 +194,11 @@ export const isPrevMonthDisabled = ( month?: number, year: number }) => { - if (minParts) { - if (typeof minParts.month !== 'undefined') { - /** - * Disables the previous month if the current date is either at the minimum - * month or before the minimum range. - * - * i.e.: - * - Ref: 09/2021 - * - Min: 10/2021 - */ - if (refParts.month <= minParts.month && refParts.year <= minParts.year) { - return true; - } - } - /** - * The minimum range only includes a year. Compare that the current date's year - * is either at the minimum year or before the minimum year. - * - * i.e: - * - Ref: 2021 - * - Min: 2021 - */ - if (refParts.month === 1) { - if (refParts.year <= minParts.year) { - return true; - } - } else if (refParts.year < minParts.year) { - return true; - } - } - if (maxParts) { - if (typeof maxParts.month !== 'undefined') { - /** - * In situations where the current date is outside the bounds of the upper (max) range, - * we need to check if the previous month would return the date range into the valid range. - * - * i.e.: - * - Date: 11/16/2021 - * - Max: 10/2021 - * - Min: 09/2021 - * - * If it does, we allow the previous button to navigate back one step, otherwise we lock - * navigation and require they use the month/year selector. - */ - if (refParts.month - 1 === 0) { - // The reference month is in January, so we need to step back to December of the previous year. - if (12 > maxParts.month && refParts.year - 1 === maxParts.year) { - return true; - } - } else if (refParts.month - 1 > maxParts.month && refParts.year === maxParts.year) { - // Otherwise we are comparing if the previous month in the same year is within - // the maximum date range. - return true; - } - } - } - return false; + const prevMonth = getPreviousMonth(refParts); + return isMonthDisabled(prevMonth, { + minParts, + maxParts + }); } /** @@ -226,18 +208,15 @@ export const isPrevMonthDisabled = ( export const isNextMonthDisabled = ( refParts: { month: number, - year: number + year: number, + day: number | null }, maxParts?: { month?: number, year: number }) => { - if (maxParts) { - if (typeof maxParts.month !== 'undefined' && refParts.month >= maxParts.month && refParts.year >= maxParts.year) { - return true; - } else if (refParts.year > maxParts.year) { - return true; - } - } - return false; + const nextMonth = getNextMonth(refParts); + return isMonthDisabled(nextMonth, { + maxParts + }); } From 16d46c8038413cd1264e878c21832b9ded6ec376 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Mon, 31 Jan 2022 12:33:57 -0500 Subject: [PATCH 11/12] fix(datetime): swipe gesture threshold on desktop browsers Resolves #24482 --- core/src/components/datetime/datetime.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 197386a6ecd..07d16218534 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -792,6 +792,12 @@ export class Datetime implements ComponentInterface { }); } + const threshold = mode === 'ios' && + // tslint:disable-next-line + typeof navigator !== 'undefined' && + navigator.maxTouchPoints > 1 ? + [0.7, 1] : 1; + /** * Listen on the first month to * prepend a new month and on the last @@ -811,13 +817,13 @@ export class Datetime implements ComponentInterface { * something WebKit does. */ endIO = new IntersectionObserver(ev => ioCallback('end', ev), { - threshold: mode === 'ios' ? [0.7, 1] : 1, + threshold, root: calendarBodyRef }); endIO.observe(endMonth); startIO = new IntersectionObserver(ev => ioCallback('start', ev), { - threshold: mode === 'ios' ? [0.7, 1] : 1, + threshold, root: calendarBodyRef }); startIO.observe(startMonth); From 06c271069e1a5958064cea42291ad6d37abc8b34 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Mon, 31 Jan 2022 13:15:14 -0500 Subject: [PATCH 12/12] chore(): remove isMonthSwipeDisabled, simplify month disabled logic --- core/src/components/datetime/datetime.tsx | 26 +++-- .../components/datetime/test/state.spec.ts | 26 ++--- core/src/components/datetime/utils/state.ts | 94 +++---------------- 3 files changed, 46 insertions(+), 100 deletions(-) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 07d16218534..0ddfbe3eb4e 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -57,7 +57,7 @@ import { import { getCalendarDayState, isDayDisabled, - isMonthSwipeDisabled, + isMonthDisabled, isNextMonthDisabled, isPrevMonthDisabled } from './utils/state'; @@ -719,7 +719,10 @@ export class Datetime implements ComponentInterface { const { month, year, day } = refMonthFn(this.workingParts); - if (isMonthSwipeDisabled(month, year, this.workingParts, this.minParts, this.maxParts)) { + if (isMonthDisabled({ month, year, day: null }, { + minParts: this.minParts, + maxParts: this.maxParts + })) { return; } @@ -1220,14 +1223,25 @@ export class Datetime implements ComponentInterface { private renderMonth(month: number, year: number) { const yearAllowed = this.parsedYearValues === undefined || this.parsedYearValues.includes(year); const monthAllowed = this.parsedMonthValues === undefined || this.parsedMonthValues.includes(month); - const isMonthDisabled = !yearAllowed || !monthAllowed; - const swipeDisabled = isMonthSwipeDisabled(month, year, this.workingParts, this.minParts, this.maxParts); + const isCalMonthDisabled = !yearAllowed || !monthAllowed; + const swipeDisabled = isMonthDisabled({ + month, + year, + day: null + }, { + minParts: this.minParts, + maxParts: this.maxParts + }); + // The working month should never have swipe disabled. + // Otherwise the CSS scroll snap will not work and the user + // can free-scroll the calendar. + const isWorkingMonth = this.workingParts.month === month && this.workingParts.year === year; return (
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => { @@ -1243,7 +1257,7 @@ export class Datetime implements ComponentInterface { data-year={year} data-index={index} data-day-of-week={dayOfWeek} - disabled={isMonthDisabled || disabled} + disabled={isCalMonthDisabled || disabled} class={{ 'calendar-day-padding': day === null, 'calendar-day': true, diff --git a/core/src/components/datetime/test/state.spec.ts b/core/src/components/datetime/test/state.spec.ts index 64790796e7c..9d4b2af1105 100644 --- a/core/src/components/datetime/test/state.spec.ts +++ b/core/src/components/datetime/test/state.spec.ts @@ -80,21 +80,21 @@ describe('isPrevMonthDisabled()', () => { it('should return true', () => { // Date month is before min month, in the same year - expect(isPrevMonthDisabled({ month: 5, year: 2021, day: null }, { month: 6, year: 2021 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 5, year: 2021, day: null }, { month: 6, year: 2021, day: null })).toEqual(true); // Date month and year is the same as min month and year - expect(isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { month: 1, year: 2021 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { month: 1, year: 2021, day: null })).toEqual(true); // Date year is the same as min year (month not provided) - expect(isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { year: 2021 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 1, year: 2021, day: null }, { year: 2021, month: null, day: null })).toEqual(true); // Date year is less than the min year (month not provided) - expect(isPrevMonthDisabled({ month: 5, year: 2021, day: null }, { year: 2022 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 5, year: 2021, day: null }, { year: 2022, month: null, day: null })).toEqual(true); // Date is above the maximum bounds and the previous month does not does not fall within the // min-max range. - expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null }, { month: 9, year: 2021 }, { month: 10, year: 2021 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null }, { month: 9, year: 2021, day: null }, { month: 10, year: 2021, day: null })).toEqual(true); // Date is above the maximum bounds and a year ahead of the max range. The previous month/year // does not fall within the min-max range. - expect(isPrevMonthDisabled({ month: 1, year: 2022, day: null }, { month: 9, year: 2021 }, { month: 10, year: 2021 })).toEqual(true); + expect(isPrevMonthDisabled({ month: 1, year: 2022, day: null }, { month: 9, year: 2021, day: null }, { month: 10, year: 2021, day: null })).toEqual(true); }); @@ -103,8 +103,8 @@ describe('isPrevMonthDisabled()', () => { expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null })).toEqual(false); // Date year is the same as min year, // but can navigate to a previous month without reducing the year. - expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null }, { year: 2021 })).toEqual(false); - expect(isPrevMonthDisabled({ month: 2, year: 2021, day: null }, { year: 2021 })).toEqual(false); + expect(isPrevMonthDisabled({ month: 12, year: 2021, day: null }, { year: 2021, month: null, day: null })).toEqual(false); + expect(isPrevMonthDisabled({ month: 2, year: 2021, day: null }, { year: 2021, month: null, day: null })).toEqual(false); }); }); @@ -113,19 +113,19 @@ describe('isNextMonthDisabled()', () => { it('should return true', () => { // Date month is the same as max month (in the same year) - expect(isNextMonthDisabled({ month: 10, year: 2021 }, { month: 10, year: 2021 })).toEqual(true); + expect(isNextMonthDisabled({ month: 10, year: 2021, day: null }, { month: 10, year: 2021, day: null })).toEqual(true); // Date month is after the max month (in the same year) - expect(isNextMonthDisabled({ month: 10, year: 2021 }, { month: 9, year: 2021 })).toEqual(true); + expect(isNextMonthDisabled({ month: 10, year: 2021, day: null }, { month: 9, year: 2021, day: null })).toEqual(true); // Date year is after the max month and year - expect(isNextMonthDisabled({ month: 10, year: 2022 }, { month: 12, year: 2021 })).toEqual(true); + expect(isNextMonthDisabled({ month: 10, year: 2022, day: null }, { month: 12, year: 2021, day: null })).toEqual(true); }); it('should return false', () => { // No max range provided - expect(isNextMonthDisabled({ month: 10, year: 2021 })).toBe(false); + expect(isNextMonthDisabled({ month: 10, year: 2021, day: null })).toBe(false); // Date month is before max month and is the previous month, // so that navigating the next month would re-enter the max range - expect(isNextMonthDisabled({ month: 10, year: 2021 }, { month: 11, year: 2021 })).toEqual(false); + expect(isNextMonthDisabled({ month: 10, year: 2021, day: null }, { month: 11, year: 2021, day: null })).toEqual(false); }); }); diff --git a/core/src/components/datetime/utils/state.ts b/core/src/components/datetime/utils/state.ts index 134f6a80b28..4c2a4f93d6e 100644 --- a/core/src/components/datetime/utils/state.ts +++ b/core/src/components/datetime/utils/state.ts @@ -16,45 +16,6 @@ export const isYearDisabled = (refYear: number, minParts?: DatetimeParts, maxPar return false; } -export const isMonthSwipeDisabled = ( - refMonth: number, - refYear: number, - workingParts?: DatetimeParts, - minParts?: { month?: number, year: number }, - maxParts?: { month?: number, year: number }) => { - if (workingParts && refMonth === workingParts.month && refYear === workingParts.year) { - /** - * Do not disable the working month, if it's the current month being rendered. - * - * If the working month is disabled, scroll snap will not clip to the month - * and will scroll the user to the first "enabled" calendar month. - */ - return false; - } - - if (minParts) { - if (typeof minParts.month !== 'undefined') { - // If the minimum parts contains a month, compare both the month and year - if (refMonth < minParts.month && refYear <= minParts.year) { - return true; - } - // Otherwise only compare the year range - } else if (refYear < minParts.year) { - return true; - } - } - - if (maxParts) { - if (typeof maxParts.month !== 'undefined' && refMonth > maxParts.month && refYear >= maxParts.year) { - return true; - } else if (refYear > maxParts.year) { - return true; - } - } - - return false; -} - /** * Returns true if a given day should * not be interactive according to its value, @@ -147,31 +108,19 @@ export const getCalendarDayState = ( * Returns `true` if the month is disabled given the * current date value and min/max date constraints. */ -export const isMonthDisabled = (refParts: { month: number, year: number }, { minParts, maxParts }: { - minParts?: { month?: number, year: number }, - maxParts?: { - month?: number, year: number - } +export const isMonthDisabled = (refParts: DatetimeParts, { minParts, maxParts }: { + minParts?: DatetimeParts, + maxParts?: DatetimeParts }) => { - // If the min date is set and the year is less than the min year. - if (minParts && minParts.year > refParts.year) { - return true; - } - // If the max date is set and the year is greater than the max year. - if (maxParts && maxParts.year < refParts.year) { - return true; - } - // If the min date is set and the year is the same as the min year, - // but the month is less than the min month. - if (minParts && minParts.year === refParts.year && minParts.month !== undefined && minParts.month > refParts.month) { + // If the year is disabled then the month is disabled. + if (isYearDisabled(refParts.year, minParts, maxParts)) { return true; } - // If the max date is set and the year is the same as the max year, - // but the month is greater than the max month. - if (maxParts && maxParts.year === refParts.year && maxParts.month !== undefined && maxParts.month < refParts.month) { + // If the date value is before the min date, then the month is disabled. + // If the date value is after the max date, then the month is disabled. + if (minParts && isBefore(refParts, minParts) || maxParts && isAfter(refParts, maxParts)) { return true; } - return false; } @@ -181,19 +130,9 @@ export const isMonthDisabled = (refParts: { month: number, year: number }, { min * previous navigation button is disabled. */ export const isPrevMonthDisabled = ( - refParts: { - month: number, - year: number, - day: number | null - }, - minParts?: { - month?: number, - year: number - }, - maxParts?: { - month?: number, - year: number - }) => { + refParts: DatetimeParts, + minParts?: DatetimeParts, + maxParts?: DatetimeParts) => { const prevMonth = getPreviousMonth(refParts); return isMonthDisabled(prevMonth, { minParts, @@ -206,15 +145,8 @@ export const isPrevMonthDisabled = ( * determine if the next navigation button is disabled. */ export const isNextMonthDisabled = ( - refParts: { - month: number, - year: number, - day: number | null - }, - maxParts?: { - month?: number, - year: number - }) => { + refParts: DatetimeParts, + maxParts?: DatetimeParts) => { const nextMonth = getNextMonth(refParts); return isMonthDisabled(nextMonth, { maxParts