mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
fix(datetime): update active calendar display when value changes (#24244)
This commit is contained in:
@ -93,6 +93,12 @@ export class Datetime implements ComponentInterface {
|
|||||||
private minParts?: any;
|
private minParts?: any;
|
||||||
private maxParts?: 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() showMonthAndYear = false;
|
||||||
|
|
||||||
@State() activeParts: DatetimeParts = {
|
@State() activeParts: DatetimeParts = {
|
||||||
@ -291,6 +297,25 @@ export class Datetime implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Watch('value')
|
@Watch('value')
|
||||||
protected valueChanged() {
|
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.emitStyle();
|
||||||
this.ionChange.emit({
|
this.ionChange.emit({
|
||||||
value: this.value
|
value: this.value
|
||||||
@ -911,7 +936,8 @@ export class Datetime implements ComponentInterface {
|
|||||||
tzOffset,
|
tzOffset,
|
||||||
ampm: hour >= 12 ? 'pm' : 'am'
|
ampm: hour >= 12 ? 'pm' : 'am'
|
||||||
}
|
}
|
||||||
this.activeParts = {
|
|
||||||
|
this.activePartsClone = this.activeParts = {
|
||||||
month,
|
month,
|
||||||
day,
|
day,
|
||||||
year,
|
year,
|
||||||
@ -951,6 +977,10 @@ export class Datetime implements ComponentInterface {
|
|||||||
this.ionBlur.emit();
|
this.ionBlur.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private hasValue = () => {
|
||||||
|
return this.value != null && this.value !== '';
|
||||||
|
}
|
||||||
|
|
||||||
private nextMonth = () => {
|
private nextMonth = () => {
|
||||||
const { calendarBodyRef } = this;
|
const { calendarBodyRef } = this;
|
||||||
if (!calendarBodyRef) { return; }
|
if (!calendarBodyRef) { return; }
|
||||||
@ -1135,7 +1165,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => {
|
{getDaysOfMonth(month, year, this.firstDayOfWeek % 7).map((dateObject, index) => {
|
||||||
const { day, dayOfWeek } = dateObject;
|
const { day, dayOfWeek } = dateObject;
|
||||||
const referenceParts = { month, day, year };
|
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 (
|
return (
|
||||||
<button
|
<button
|
||||||
@ -1213,21 +1243,22 @@ export class Datetime implements ComponentInterface {
|
|||||||
ampmItems: PickerColumnItem[],
|
ampmItems: PickerColumnItem[],
|
||||||
use24Hour: boolean
|
use24Hour: boolean
|
||||||
) {
|
) {
|
||||||
const { color, workingParts } = this;
|
const { color, activePartsClone, workingParts } = this;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ion-picker-internal>
|
<ion-picker-internal>
|
||||||
<ion-picker-column-internal
|
<ion-picker-column-internal
|
||||||
color={color}
|
color={color}
|
||||||
value={workingParts.hour}
|
value={activePartsClone.hour}
|
||||||
items={hoursItems}
|
items={hoursItems}
|
||||||
numericInput
|
numericInput
|
||||||
onIonChange={(ev: CustomEvent) => {
|
onIonChange={(ev: CustomEvent) => {
|
||||||
this.setWorkingParts({
|
this.setWorkingParts({
|
||||||
...this.workingParts,
|
...workingParts,
|
||||||
hour: ev.detail.value
|
hour: ev.detail.value
|
||||||
});
|
});
|
||||||
this.setActiveParts({
|
this.setActiveParts({
|
||||||
...this.activeParts,
|
...activePartsClone,
|
||||||
hour: ev.detail.value
|
hour: ev.detail.value
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1236,16 +1267,16 @@ export class Datetime implements ComponentInterface {
|
|||||||
></ion-picker-column-internal>
|
></ion-picker-column-internal>
|
||||||
<ion-picker-column-internal
|
<ion-picker-column-internal
|
||||||
color={color}
|
color={color}
|
||||||
value={workingParts.minute}
|
value={activePartsClone.minute}
|
||||||
items={minutesItems}
|
items={minutesItems}
|
||||||
numericInput
|
numericInput
|
||||||
onIonChange={(ev: CustomEvent) => {
|
onIonChange={(ev: CustomEvent) => {
|
||||||
this.setWorkingParts({
|
this.setWorkingParts({
|
||||||
...this.workingParts,
|
...workingParts,
|
||||||
minute: ev.detail.value
|
minute: ev.detail.value
|
||||||
});
|
});
|
||||||
this.setActiveParts({
|
this.setActiveParts({
|
||||||
...this.activeParts,
|
...activePartsClone,
|
||||||
minute: ev.detail.value
|
minute: ev.detail.value
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1254,19 +1285,19 @@ export class Datetime implements ComponentInterface {
|
|||||||
></ion-picker-column-internal>
|
></ion-picker-column-internal>
|
||||||
{ !use24Hour && <ion-picker-column-internal
|
{ !use24Hour && <ion-picker-column-internal
|
||||||
color={color}
|
color={color}
|
||||||
value={workingParts.ampm}
|
value={activePartsClone.ampm}
|
||||||
items={ampmItems}
|
items={ampmItems}
|
||||||
onIonChange={(ev: CustomEvent) => {
|
onIonChange={(ev: CustomEvent) => {
|
||||||
const hour = calculateHourFromAMPM(this.workingParts, ev.detail.value);
|
const hour = calculateHourFromAMPM(workingParts, ev.detail.value);
|
||||||
|
|
||||||
this.setWorkingParts({
|
this.setWorkingParts({
|
||||||
...this.workingParts,
|
...workingParts,
|
||||||
ampm: ev.detail.value,
|
ampm: ev.detail.value,
|
||||||
hour
|
hour
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setActiveParts({
|
this.setActiveParts({
|
||||||
...this.workingParts,
|
...activePartsClone,
|
||||||
ampm: ev.detail.value,
|
ampm: ev.detail.value,
|
||||||
hour
|
hour
|
||||||
});
|
});
|
||||||
@ -1309,7 +1340,7 @@ export class Datetime implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{getFormattedTime(this.workingParts, use24Hour)}
|
{getFormattedTime(this.activePartsClone, use24Hour)}
|
||||||
</button>,
|
</button>,
|
||||||
<ion-popover
|
<ion-popover
|
||||||
alignment="center"
|
alignment="center"
|
||||||
|
37
core/src/components/datetime/test/set-value/e2e.ts
Normal file
37
core/src/components/datetime/test/set-value/e2e.ts
Normal file
@ -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');
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
42
core/src/components/datetime/test/set-value/index.html
Normal file
42
core/src/components/datetime/test/set-value/index.html
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" dir="ltr">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Datetime - Set Value</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
|
||||||
|
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||||
|
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||||
|
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||||
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
|
<style>
|
||||||
|
h2 {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
|
||||||
|
color: #6f7378;
|
||||||
|
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-datetime {
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<ion-app>
|
||||||
|
<ion-header translucent="true">
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Datetime - Set Value</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content class="ion-padding">
|
||||||
|
<ion-datetime value="2021-11-22T14:23:00.000Z" locale="en-US"></ion-datetime>
|
||||||
|
</ion-content>
|
||||||
|
</ion-app>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
@ -24,6 +24,7 @@ import { PickerColumnItem } from './picker-column-internal-interfaces';
|
|||||||
export class PickerColumnInternal implements ComponentInterface {
|
export class PickerColumnInternal implements ComponentInterface {
|
||||||
private destroyScrollListener?: () => void;
|
private destroyScrollListener?: () => void;
|
||||||
private hapticsStarted = false;
|
private hapticsStarted = false;
|
||||||
|
private isColumnVisible = false;
|
||||||
|
|
||||||
@State() isActive = false;
|
@State() isActive = false;
|
||||||
|
|
||||||
@ -64,6 +65,11 @@ export class PickerColumnInternal implements ComponentInterface {
|
|||||||
|
|
||||||
@Watch('value')
|
@Watch('value')
|
||||||
valueChange() {
|
valueChange() {
|
||||||
|
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;
|
const { items, value } = this;
|
||||||
this.scrollActiveItemIntoView();
|
this.scrollActiveItemIntoView();
|
||||||
|
|
||||||
@ -72,6 +78,7 @@ export class PickerColumnInternal implements ComponentInterface {
|
|||||||
this.ionChange.emit(findItem);
|
this.ionChange.emit(findItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only setup scroll listeners
|
* Only setup scroll listeners
|
||||||
@ -86,11 +93,13 @@ export class PickerColumnInternal implements ComponentInterface {
|
|||||||
if (ev.isIntersecting) {
|
if (ev.isIntersecting) {
|
||||||
this.scrollActiveItemIntoView();
|
this.scrollActiveItemIntoView();
|
||||||
this.initializeScrollListener();
|
this.initializeScrollListener();
|
||||||
|
this.isColumnVisible = true;
|
||||||
} else {
|
} else {
|
||||||
if (this.destroyScrollListener) {
|
if (this.destroyScrollListener) {
|
||||||
this.destroyScrollListener();
|
this.destroyScrollListener();
|
||||||
this.destroyScrollListener = undefined;
|
this.destroyScrollListener = undefined;
|
||||||
}
|
}
|
||||||
|
this.isColumnVisible = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
new IntersectionObserver(visibleCallback, { threshold: 0.01 }).observe(this.el);
|
new IntersectionObserver(visibleCallback, { threshold: 0.01 }).observe(this.el);
|
||||||
|
Reference in New Issue
Block a user