diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx
index 356cbc8ebd..fe1ece975f 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 trigger 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 = {
@@ -291,6 +297,25 @@ export class Datetime implements ComponentInterface {
*/
@Watch('value')
protected valueChanged() {
+ if (this.hasValue()) {
+ /**
+ * 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 = {
+ ...this.activeParts,
+ month,
+ day,
+ year,
+ hour,
+ minute
+ }
+ }
+
this.emitStyle();
this.ionChange.emit({
value: this.value
@@ -911,7 +936,8 @@ export class Datetime implements ComponentInterface {
tzOffset,
ampm: hour >= 12 ? 'pm' : 'am'
}
- this.activeParts = {
+
+ this.activePartsClone = this.activeParts = {
month,
day,
year,
@@ -951,6 +977,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; }
@@ -1135,7 +1165,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 (
,
{
+
+ 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 0000000000..4448abb706
--- /dev/null
+++ b/core/src/components/datetime/test/set-value/index.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+ Datetime - Set Value
+
+
+
+
+
+
+
+
+
+
+
+
+ Datetime - Set Value
+
+
+
+
+
+
+
+
+
diff --git a/core/src/components/picker-column-internal/picker-column-internal.tsx b/core/src/components/picker-column-internal/picker-column-internal.tsx
index 4168a765f5..efc4096d0c 100644
--- a/core/src/components/picker-column-internal/picker-column-internal.tsx
+++ b/core/src/components/picker-column-internal/picker-column-internal.tsx
@@ -24,6 +24,7 @@ import { PickerColumnItem } from './picker-column-internal-interfaces';
export class PickerColumnInternal implements ComponentInterface {
private destroyScrollListener?: () => 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);