Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 27 additions & 18 deletions core/src/components/datetime/datetime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1716,8 +1716,6 @@ export class Datetime implements ComponentInterface {
private renderCombinedDatePickerColumn() {
const { defaultParts, disabled, workingParts, locale, minParts, maxParts, todayParts, isDateEnabled } = this;

const activePart = this.getActivePartsWithFallback();

/**
* By default, generate a range of 3 months:
* Previous month, current month, and next month
Expand Down Expand Up @@ -1801,8 +1799,11 @@ export class Datetime implements ComponentInterface {
const { value } = ev.detail;
const findPart = parts.find(({ month, day, year }) => value === `${year}-${month}-${day}`);

// Read live so parts a sibling column just committed are included.
const activePart = this.getActivePartsWithFallback();

this.setWorkingParts({
...workingParts,
...this.workingParts,
...findPart,
});

Expand Down Expand Up @@ -1908,7 +1909,6 @@ export class Datetime implements ComponentInterface {

const { disabled, workingParts } = this;

const activePart = this.getActivePartsWithFallback();
const pickerColumnValue = (workingParts.day !== null ? workingParts.day : this.defaultParts.day) ?? undefined;

return (
Expand All @@ -1920,8 +1920,11 @@ export class Datetime implements ComponentInterface {
disabled={disabled}
value={pickerColumnValue}
onIonChange={(ev: CustomEvent) => {
// Read live so parts a sibling column just committed are included.
const activePart = this.getActivePartsWithFallback();

this.setWorkingParts({
...workingParts,
...this.workingParts,
day: ev.detail.value,
});

Expand Down Expand Up @@ -1955,8 +1958,6 @@ export class Datetime implements ComponentInterface {

const { disabled, workingParts } = this;

const activePart = this.getActivePartsWithFallback();

return (
<ion-picker-column
part={WHEEL_PART}
Expand All @@ -1966,8 +1967,11 @@ export class Datetime implements ComponentInterface {
disabled={disabled}
value={workingParts.month}
onIonChange={(ev: CustomEvent) => {
// Read live so parts a sibling column just committed are included.
const activePart = this.getActivePartsWithFallback();

this.setWorkingParts({
...workingParts,
...this.workingParts,
month: ev.detail.value,
});

Expand Down Expand Up @@ -2003,8 +2007,6 @@ export class Datetime implements ComponentInterface {

const { disabled, workingParts } = this;

const activePart = this.getActivePartsWithFallback();

return (
<ion-picker-column
part={WHEEL_PART}
Expand All @@ -2014,8 +2016,11 @@ export class Datetime implements ComponentInterface {
disabled={disabled}
value={workingParts.year}
onIonChange={(ev: CustomEvent) => {
// Read live so parts a sibling column just committed are included.
const activePart = this.getActivePartsWithFallback();

this.setWorkingParts({
...workingParts,
...this.workingParts,
year: ev.detail.value,
});

Expand Down Expand Up @@ -2079,7 +2084,7 @@ export class Datetime implements ComponentInterface {
}

private renderHourPickerColumn(hoursData: WheelColumnOption[]) {
const { disabled, workingParts } = this;
const { disabled } = this;
if (hoursData.length === 0) return [];

const activePart = this.getActivePartsWithFallback();
Expand All @@ -2093,8 +2098,9 @@ export class Datetime implements ComponentInterface {
value={activePart.hour}
numericInput
onIonChange={(ev: CustomEvent) => {
// Read live so parts a sibling column just committed are included.
this.setWorkingParts({
...workingParts,
...this.workingParts,
hour: ev.detail.value,
});

Expand All @@ -2121,7 +2127,7 @@ export class Datetime implements ComponentInterface {
);
}
private renderMinutePickerColumn(minutesData: WheelColumnOption[]) {
const { disabled, workingParts } = this;
const { disabled } = this;
if (minutesData.length === 0) return [];

const activePart = this.getActivePartsWithFallback();
Expand All @@ -2135,8 +2141,9 @@ export class Datetime implements ComponentInterface {
value={activePart.minute}
numericInput
onIonChange={(ev: CustomEvent) => {
// Read live so parts a sibling column just committed are included.
this.setWorkingParts({
...workingParts,
...this.workingParts,
minute: ev.detail.value,
});

Expand All @@ -2163,7 +2170,7 @@ export class Datetime implements ComponentInterface {
);
}
private renderDayPeriodPickerColumn(dayPeriodData: WheelColumnOption[]) {
const { disabled, workingParts } = this;
const { disabled } = this;
if (dayPeriodData.length === 0) {
return [];
}
Expand All @@ -2180,10 +2187,12 @@ export class Datetime implements ComponentInterface {
disabled={disabled}
value={activePart.ampm}
onIonChange={(ev: CustomEvent) => {
const hour = calculateHourFromAMPM(workingParts, ev.detail.value);
// Read live so parts a sibling column just committed are included.
const currentParts = this.workingParts;
const hour = calculateHourFromAMPM(currentParts, ev.detail.value);

this.setWorkingParts({
...workingParts,
...currentParts,
ampm: ev.detail.value,
hour,
});
Expand Down
101 changes: 101 additions & 0 deletions core/src/components/datetime/test/prefer-wheel/datetime.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -688,3 +688,104 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
});
});
});

/**
* This behavior does not vary across modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
/**
* The column sits in the datetime's Shadow DOM, so a document-level listener
* sees the press retargeted to the datetime host.
*/
test.describe(title('datetime: wheel value'), () => {
test('should give an outside click handler the date the wheel is showing', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/30449',
});

await page.setContent(
`
<ion-datetime locale="en-US" presentation="date" prefer-wheel="true" value="2022-06-15"></ion-datetime>
<button id="save">Save</button>
`,
config
);

await page.locator('.datetime-ready').waitFor();

await page.evaluate(() => {
const datetime = document.querySelector('ion-datetime')!;
const column = datetime.shadowRoot!.querySelector('ion-picker-column.year-column')!;
const scrollEl = column.shadowRoot!.querySelector('.picker-opts')!;
const w = window as any;

w.lastScrollAt = 0;
scrollEl.addEventListener('scroll', () => {
w.lastScrollAt = performance.now();
});

/**
* Stands in for an application's own Save button, which reads the
* datetime's value when it is clicked.
*/
document.querySelector('#save')!.addEventListener('click', () => {
w.onSave = {
datetimeValue: datetime.value,
visibleYear: String(column.querySelector<HTMLIonPickerColumnOptionElement>('.option-active')?.value ?? ''),
};
});

w.startScroll = () => scrollEl.scrollTo({ top: scrollEl.scrollHeight, behavior: 'smooth' });

/**
* Presses Save in the same frame the wheel is first seen showing a
* different year while still scrolling. Done in the page so no round trip
* can let the scroll finish first, which would leave nothing to race.
*/
w.pressSaveWhenMidScroll = () =>
new Promise<void>((resolve, reject) => {
const deadline = performance.now() + 5000;

const poll = () => {
const highlighted = column.querySelector<HTMLIonPickerColumnOptionElement>('.option-active');
const isScrolling = performance.now() - w.lastScrollAt < 100;

if (highlighted !== null && String(highlighted.value) !== '2022' && isScrolling) {
const save = document.querySelector('#save')!;
save.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, composed: true }));
save.dispatchEvent(new MouseEvent('click', { bubbles: true, composed: true }));
resolve();
} else if (performance.now() > deadline) {
reject(new Error('the wheel never showed a year other than the one it was reporting'));
} else {
requestAnimationFrame(poll);
}
};

requestAnimationFrame(poll);
});
});

/**
* Press the column the way a drag would, so the scroll that follows counts
* as the user's. Pressing dead center lands on the year already selected,
* so the press itself does not change the value.
*/
const column = (await page.locator('.year-column').boundingBox())!;
await page.mouse.move(column.x + column.width / 2, column.y + column.height / 2);
await page.mouse.down();
await page.mouse.up();

await page.evaluate(() => (window as any).startScroll());
await page.evaluate(() => (window as any).pressSaveWhenMidScroll());

const onSave = await page.evaluate(() => (window as any).onSave);

expect(onSave.visibleYear).not.toBe('2022');

// The value the Save button saw is the year the user could see.
expect(onSave.datetimeValue.split('-')[0]).toBe(onSave.visibleYear);
});
});
});
Loading
Loading