fix(datetime): switching months in wheel picker now selected nearest neighbor (#25559)

This commit is contained in:
Liam DeBeasi
2022-06-30 13:16:29 -04:00
committed by GitHub
parent fba4cc07cb
commit dd256e1313
2 changed files with 239 additions and 0 deletions

View File

@ -36,6 +36,70 @@ export class PickerColumnInternal implements ComponentInterface {
* A list of options to be displayed in the picker
*/
@Prop() items: PickerColumnItem[] = [];
@Watch('items')
itemsChange(currentItems: PickerColumnItem[], previousItems: PickerColumnItem[]) {
const { value } = this;
/**
* When the items change, it is possible for the item
* that was selected to no longer exist. In that case, we need
* to automatically select the nearest item. If we do not,
* then the scroll position will be reset to zero and it will
* look like the first item was automatically selected.
*
* If we cannot find a closest item then we do nothing, and
* the browser will reset the scroll position to 0.
*/
const findCurrentItem = currentItems.find((item) => item.value === value);
if (!findCurrentItem) {
/**
* The default behavior is to assume
* that the new set of data is similar to the old
* set of data, just with some items filtered out.
* We walk backwards through the data to find the
* closest enabled picker item and select it.
*
* Developers can also swap the items out for an entirely
* new set of data. In that case, the value we select
* here likely will not make much sense. For this use case,
* developers should update the `value` prop themselves
* when swapping out the data.
*/
const findPreviousItemIndex = previousItems.findIndex((item) => item.value === value);
if (findPreviousItemIndex === -1) {
return;
}
/**
* Step through the current items backwards
* until we find a neighbor we can select.
* We start at the last known location of the
* current selected item in order to
* account for data that has been added. This
* search prioritizes stability in that it
* tries to keep the scroll position as close
* to where it was before the update.
* Before Items: ['a', 'b', 'c'], Selected Value: 'b'
* After Items: ['a', 'dog', 'c']
* Even though 'dog' is a different item than 'b',
* it is the closest item we can select while
* preserving the scroll position.
*/
let nearestItem;
for (let i = findPreviousItemIndex; i >= 0; i--) {
const item = currentItems[i];
if (item !== undefined && item.disabled !== true) {
nearestItem = item;
break;
}
}
if (nearestItem) {
this.setValue(nearestItem.value);
return;
}
}
}
/**
* The selected option in the picker.

View File

@ -0,0 +1,175 @@
import { expect } from '@playwright/test';
import { test } from '@utils/test/playwright';
test.describe('picker-column-internal: updating items', () => {
test('should select nearest neighbor when updating items', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '5', value: 5 },
];
column.value = 5;
</script>
`);
const pickerColumn = page.locator('ion-picker-column-internal');
await expect(pickerColumn).toHaveJSProperty('value', 5);
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => {
el.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
];
});
await page.waitForChanges();
await expect(pickerColumn).toHaveJSProperty('value', 2);
});
test('should select same position item even if item value is different', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '5', value: 5 },
];
column.value = 5;
</script>
`);
const pickerColumn = page.locator('ion-picker-column-internal');
await expect(pickerColumn).toHaveJSProperty('value', 5);
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => {
el.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '1000', value: 1000 },
];
});
await page.waitForChanges();
await expect(pickerColumn).toHaveJSProperty('value', 1000);
});
test('should not select a disabled item', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '5', value: 5 },
];
column.value = 5;
</script>
`);
const pickerColumn = page.locator('ion-picker-column-internal');
await expect(pickerColumn).toHaveJSProperty('value', 5);
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => {
el.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3, disabled: true },
];
});
await page.waitForChanges();
await expect(pickerColumn).toHaveJSProperty('value', 2);
});
test('should reset to the first item if no good item was found', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '5', value: 5 },
];
column.value = 5;
</script>
`);
const pickerColumn = page.locator('ion-picker-column-internal');
await expect(pickerColumn).toHaveJSProperty('value', 5);
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => {
el.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2, disabled: true },
{ text: '3', value: 3, disabled: true },
];
});
await page.waitForChanges();
await expect(pickerColumn).toHaveJSProperty('value', 1);
});
test('should still select correct value if data was added', async ({ page }) => {
await page.setContent(`
<ion-picker-internal>
<ion-picker-column-internal></ion-picker-column-internal>
</ion-picker-internal>
<script>
const column = document.querySelector('ion-picker-column-internal');
column.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '5', value: 5 },
];
column.value = 5;
</script>
`);
const pickerColumn = page.locator('ion-picker-column-internal');
await expect(pickerColumn).toHaveJSProperty('value', 5);
await pickerColumn.evaluate((el: HTMLIonPickerColumnInternalElement) => {
el.items = [
{ text: '1', value: 1 },
{ text: '2', value: 2 },
{ text: '3', value: 3 },
{ text: '4', value: 4 },
{ text: '6', value: 6 },
{ text: '7', value: 7 },
{ text: '5', value: 5 },
];
});
await page.waitForChanges();
await expect(pickerColumn).toHaveJSProperty('value', 5);
});
});