From 5e975ed4580ef148c1a3106afc5bb0990d2e0f51 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Thu, 18 Nov 2021 11:02:41 -0500 Subject: [PATCH 1/6] fix(datetime): update active calendar display when value changes --- core/src/components/datetime/datetime.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 356cbc8ebd6..f14fd346b74 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -291,6 +291,9 @@ export class Datetime implements ComponentInterface { */ @Watch('value') protected valueChanged() { + if (this.hasValue()) { + this.processValue(this.value); + } this.emitStyle(); this.ionChange.emit({ value: this.value @@ -951,6 +954,10 @@ export class Datetime implements ComponentInterface { this.ionBlur.emit(); } + private hasValue = () => { + return this.value != null && this.value !== ''; + } + private nextMonth = () => { const { calendarBodyRef } = this; if (!calendarBodyRef) { return; } From 454aedf18a72e6982cea649adb493b999e9e36d7 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Thu, 18 Nov 2021 16:50:15 -0500 Subject: [PATCH 2/6] fix(datetime): update the active date value in picker --- core/src/components/datetime/datetime.tsx | 25 ++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index f14fd346b74..5de24c5f173 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -292,7 +292,30 @@ export class Datetime implements ComponentInterface { @Watch('value') protected valueChanged() { if (this.hasValue()) { - this.processValue(this.value); + const { month, day, year, hour, minute, tzOffset } = parseDate(this.value); + /** + * Mutates the activeParts to re-render the active selected date + * correctly when the value is programmatically updated. + */ + this.activeParts = { + month, + day, + year, + hour, + minute, + tzOffset, + ampm: hour >= 12 ? 'pm' : 'am' + } + /** + * We only mutate the hour, minute and am/pm of the workingParts so that the + * time dial can correctly reflect the updated value when active. + */ + this.workingParts = { + ...this.workingParts, + hour, + minute, + ampm: hour >= 12 ? 'pm' : 'am' + } } this.emitStyle(); this.ionChange.emit({ From 27bd31703cc1282f9cc4d405533a0813642fcd72 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Tue, 23 Nov 2021 12:13:58 -0500 Subject: [PATCH 3/6] fix(datetime): update the display to match the current value --- core/src/components/datetime/datetime.tsx | 51 +++++++++---------- .../picker-column-internal.tsx | 19 +++++-- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 5de24c5f173..054e009fec2 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -93,6 +93,12 @@ export class Datetime implements ComponentInterface { private minParts?: any; private maxParts?: any; + /** + * Duplicate reference to `activeParts` that does not rigger a re-render of the component. + * Allows caching an instance of the `activeParts` in between render cycles. + */ + private activePartsClone!: DatetimeParts; + @State() showMonthAndYear = false; @State() activeParts: DatetimeParts = { @@ -292,31 +298,22 @@ export class Datetime implements ComponentInterface { @Watch('value') protected valueChanged() { if (this.hasValue()) { - const { month, day, year, hour, minute, tzOffset } = parseDate(this.value); /** - * Mutates the activeParts to re-render the active selected date - * correctly when the value is programmatically updated. + * Update the value of the cloned reference to `activeParts` to synchronize + * the date display on the current render cycle. */ - this.activeParts = { + const { month, day, year, hour, minute } = parseDate(this.value); + this.activePartsClone = { + ...this.activeParts, month, day, year, hour, - minute, - tzOffset, - ampm: hour >= 12 ? 'pm' : 'am' - } - /** - * We only mutate the hour, minute and am/pm of the workingParts so that the - * time dial can correctly reflect the updated value when active. - */ - this.workingParts = { - ...this.workingParts, - hour, - minute, - ampm: hour >= 12 ? 'pm' : 'am' + minute } } + + this.emitStyle(); this.ionChange.emit({ value: this.value @@ -937,7 +934,8 @@ export class Datetime implements ComponentInterface { tzOffset, ampm: hour >= 12 ? 'pm' : 'am' } - this.activeParts = { + + this.activePartsClone = this.activeParts = { month, day, year, @@ -1165,7 +1163,7 @@ export class Datetime implements ComponentInterface { {getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => { const { day, dayOfWeek } = dateObject; const referenceParts = { month, day, year }; - const { isActive, isToday, ariaLabel, ariaSelected, disabled } = getCalendarDayState(this.locale, referenceParts, this.activeParts, this.todayParts, this.minParts, this.maxParts, this.parsedDayValues); + const { isActive, isToday, ariaLabel, ariaSelected, disabled } = getCalendarDayState(this.locale, referenceParts, this.activePartsClone, this.todayParts, this.minParts, this.maxParts, this.parsedDayValues); return ( , void; private hapticsStarted = false; + private isColumnVisible = false; @State() isActive = false; @@ -64,12 +65,18 @@ export class PickerColumnInternal implements ComponentInterface { @Watch('value') valueChange() { - const { items, value } = this; - this.scrollActiveItemIntoView(); + if (this.isColumnVisible) { + /** + * Only scroll the active item into view and emit the value + * change, when the picker column is actively visible to the user. + */ + const { items, value } = this; + this.scrollActiveItemIntoView(); - const findItem = items.find(item => item.value === value); - if (findItem) { - this.ionChange.emit(findItem); + const findItem = items.find(item => item.value === value); + if (findItem) { + this.ionChange.emit(findItem); + } } } @@ -86,11 +93,13 @@ export class PickerColumnInternal implements ComponentInterface { if (ev.isIntersecting) { this.scrollActiveItemIntoView(); this.initializeScrollListener(); + this.isColumnVisible = true; } else { if (this.destroyScrollListener) { this.destroyScrollListener(); this.destroyScrollListener = undefined; } + this.isColumnVisible = false; } } new IntersectionObserver(visibleCallback, { threshold: 0.01 }).observe(this.el); From 80a0cdc914dc106985c00f44090d25b41ab58da7 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Tue, 23 Nov 2021 12:18:51 -0500 Subject: [PATCH 4/6] test(datetime): setting the value programmatically updates the display --- core/src/components/datetime/datetime.tsx | 1 - .../components/datetime/test/set-value/e2e.ts | 37 ++++++++++++++++ .../datetime/test/set-value/index.html | 42 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 core/src/components/datetime/test/set-value/e2e.ts create mode 100644 core/src/components/datetime/test/set-value/index.html diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 054e009fec2..6e14d1ad22a 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -313,7 +313,6 @@ export class Datetime implements ComponentInterface { } } - this.emitStyle(); this.ionChange.emit({ value: this.value diff --git a/core/src/components/datetime/test/set-value/e2e.ts b/core/src/components/datetime/test/set-value/e2e.ts new file mode 100644 index 00000000000..bc40ffb2cf0 --- /dev/null +++ b/core/src/components/datetime/test/set-value/e2e.ts @@ -0,0 +1,37 @@ +import { newE2EPage } from '@stencil/core/testing'; + +describe('datetime: setting the value', () => { + + it('should update the active date', async () => { + const page = await newE2EPage({ + url: '/src/components/datetime/test/set-value?ionic:_testing=true' + }); + + await page.$eval('ion-datetime', (elm: any) => { + elm.value = '2021-11-25T12:40:00.000Z'; + }); + + await page.waitForChanges(); + + const activeDate = await page.find('ion-datetime >>> .calendar-day-active'); + + expect(activeDate).toEqualText('25'); + }); + + it('should update the active time', async () => { + const page = await newE2EPage({ + url: '/src/components/datetime/test/set-value?ionic:_testing=true' + }); + + await page.$eval('ion-datetime', (elm: any) => { + elm.value = '2021-11-25T12:40:00.000Z'; + }); + + await page.waitForChanges(); + + const activeTime = await page.find('ion-datetime >>> .time-body'); + + expect(activeTime).toEqualText('12:40 PM'); + }) +}) + diff --git a/core/src/components/datetime/test/set-value/index.html b/core/src/components/datetime/test/set-value/index.html new file mode 100644 index 00000000000..e27a02779bb --- /dev/null +++ b/core/src/components/datetime/test/set-value/index.html @@ -0,0 +1,42 @@ + + + + + + Datetime - Set Value + + + + + + + + + + + + + Datetime - Set Value + + + + + + + + + From 01e89b63ee6f465fedd6fa6b5685f37b4fc3e429 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Tue, 23 Nov 2021 16:35:09 -0500 Subject: [PATCH 5/6] fix(datetime): localize test --- core/src/components/datetime/datetime.tsx | 9 ++++++--- core/src/components/datetime/test/set-value/index.html | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 6e14d1ad22a..b1ffc2015de 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -94,7 +94,7 @@ export class Datetime implements ComponentInterface { private maxParts?: any; /** - * Duplicate reference to `activeParts` that does not rigger a re-render of the component. + * Duplicate reference to `activeParts` that does not trigger a re-render of the component. * Allows caching an instance of the `activeParts` in between render cycles. */ private activePartsClone!: DatetimeParts; @@ -299,8 +299,11 @@ export class Datetime implements ComponentInterface { protected valueChanged() { if (this.hasValue()) { /** - * Update the value of the cloned reference to `activeParts` to synchronize - * the date display on the current render cycle. + * Clones the value of the `activeParts` to the private clone, to update + * the date display on the current render cycle without causing another render. + * + * This allows us to update the current value's date/time display without + * refocusing or shifting the user's display (leaves the user in place). */ const { month, day, year, hour, minute } = parseDate(this.value); this.activePartsClone = { diff --git a/core/src/components/datetime/test/set-value/index.html b/core/src/components/datetime/test/set-value/index.html index e27a02779bb..4448abb706a 100644 --- a/core/src/components/datetime/test/set-value/index.html +++ b/core/src/components/datetime/test/set-value/index.html @@ -34,7 +34,7 @@ - + From 3f5a7a67db3d529db87e95f424cd29c5dc22ba70 Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Wed, 24 Nov 2021 10:50:05 -0500 Subject: [PATCH 6/6] refactor(datetime): use activePartsClone variable naming --- core/src/components/datetime/datetime.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index b1ffc2015de..fe1ece975fc 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -1243,22 +1243,22 @@ export class Datetime implements ComponentInterface { ampmItems: PickerColumnItem[], use24Hour: boolean ) { - const { color, activePartsClone: timeParts } = this; + const { color, activePartsClone, workingParts } = this; return ( { this.setWorkingParts({ - ...this.workingParts, + ...workingParts, hour: ev.detail.value }); this.setActiveParts({ - ...this.activePartsClone, + ...activePartsClone, hour: ev.detail.value }); @@ -1267,16 +1267,16 @@ export class Datetime implements ComponentInterface { > { this.setWorkingParts({ - ...this.workingParts, + ...workingParts, minute: ev.detail.value }); this.setActiveParts({ - ...this.activePartsClone, + ...activePartsClone, minute: ev.detail.value }); @@ -1285,19 +1285,19 @@ export class Datetime implements ComponentInterface { > { !use24Hour && { - const hour = calculateHourFromAMPM(this.workingParts, ev.detail.value); + const hour = calculateHourFromAMPM(workingParts, ev.detail.value); this.setWorkingParts({ - ...this.workingParts, + ...workingParts, ampm: ev.detail.value, hour }); this.setActiveParts({ - ...this.workingParts, + ...activePartsClone, ampm: ev.detail.value, hour });