Skip to content
Merged
6 changes: 3 additions & 3 deletions core/src/components/datetime/datetime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1984,7 +1984,7 @@ export class Datetime implements ComponentInterface {
});

this.setActiveParts({
...activePart,
...this.getActivePartsWithFallback(),
hour: ev.detail.value,
});

Expand Down Expand Up @@ -2024,7 +2024,7 @@ export class Datetime implements ComponentInterface {
});

this.setActiveParts({
...activePart,
...this.getActivePartsWithFallback(),
minute: ev.detail.value,
});

Expand Down Expand Up @@ -2070,7 +2070,7 @@ export class Datetime implements ComponentInterface {
});

this.setActiveParts({
...activePart,
...this.getActivePartsWithFallback(),
ampm: ev.detail.value,
hour,
});
Expand Down
138 changes: 54 additions & 84 deletions core/src/components/picker/picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -410,15 +410,61 @@ export class Picker implements ComponentInterface {
colEl: HTMLIonPickerColumnElement,
value: string,
zeroBehavior: 'start' | 'end' = 'start'
) => {
): boolean => {
if (!value) {
return false;
}

const behavior = zeroBehavior === 'start' ? /^0+/ : /0$/;
value = value.replace(behavior, '');
const option = Array.from(colEl.querySelectorAll('ion-picker-column-option')).find((el) => {
return el.disabled !== true && el.textContent!.replace(behavior, '') === value;
});

if (option) {
colEl.setValue(option.value);
}

return !!option;
};

/**
* Attempts to intelligently search the first and second
* column as if they're number columns for the provided numbers
* where the first two numbers inpu are the first column
* and the last 2 are the last column. Tries to allow for the first
* number to be ignored for situations where typos occurred.
*/
private multiColumnSearch = (
firstColumn: HTMLIonPickerColumnElement,
secondColumn: HTMLIonPickerColumnElement,
input: string
) => {
if (input.length === 0) {
return;
}

const inputArray = input.split('');
const hourValue = inputArray.slice(0, 2).join('');
const foundHour = this.searchColumn(firstColumn, hourValue);

if (inputArray.length > 2 && foundHour) {
const minuteValue = inputArray.slice(2, 4).join('');
this.searchColumn(secondColumn, minuteValue);
} else if (!foundHour && inputArray.length >= 1) {
let singleDigitHour = inputArray[0];
let singleDigitFound = this.searchColumn(firstColumn, singleDigitHour);
if (!singleDigitFound) {
inputArray.shift();
singleDigitHour = inputArray[0];
singleDigitFound = this.searchColumn(firstColumn, singleDigitHour);
}

if (singleDigitFound && inputArray.length > 1) {
const remainingDigits = inputArray.slice(1, 3).join('');
this.searchColumn(secondColumn, remainingDigits);
}
}
};

private selectMultiColumn = () => {
Expand All @@ -433,91 +479,15 @@ export class Picker implements ComponentInterface {
const lastColumn = numericPickers[1];

let value = inputEl.value;
let minuteValue;
switch (value.length) {
case 1:
this.searchColumn(firstColumn, value);
break;
case 2:
/**
* If the first character is `0` or `1` it is
* possible that users are trying to type `09`
* or `11` into the hour field, so we should look
* at that first.
*/
const firstCharacter = inputEl.value.substring(0, 1);
value = firstCharacter === '0' || firstCharacter === '1' ? inputEl.value : firstCharacter;

this.searchColumn(firstColumn, value);

/**
* If only checked the first value,
* we can check the second value
* for a match in the minutes column
*/
if (value.length === 1) {
minuteValue = inputEl.value.substring(inputEl.value.length - 1);
this.searchColumn(lastColumn, minuteValue, 'end');
}
break;
case 3:
/**
* If the first character is `0` or `1` it is
* possible that users are trying to type `09`
* or `11` into the hour field, so we should look
* at that first.
*/
const firstCharacterAgain = inputEl.value.substring(0, 1);
value =
firstCharacterAgain === '0' || firstCharacterAgain === '1'
? inputEl.value.substring(0, 2)
: firstCharacterAgain;

this.searchColumn(firstColumn, value);

/**
* If only checked the first value,
* we can check the second value
* for a match in the minutes column
*/
minuteValue = value.length === 1 ? inputEl.value.substring(1) : inputEl.value.substring(2);

this.searchColumn(lastColumn, minuteValue, 'end');
break;
case 4:
/**
* If the first character is `0` or `1` it is
* possible that users are trying to type `09`
* or `11` into the hour field, so we should look
* at that first.
*/
const firstCharacterAgainAgain = inputEl.value.substring(0, 1);
value =
firstCharacterAgainAgain === '0' || firstCharacterAgainAgain === '1'
? inputEl.value.substring(0, 2)
: firstCharacterAgainAgain;
this.searchColumn(firstColumn, value);
if (value.length > 4) {
const startIndex = inputEl.value.length - 4;
const newString = inputEl.value.substring(startIndex);

/**
* If only checked the first value,
* we can check the second value
* for a match in the minutes column
*/
const minuteValueAgain =
value.length === 1
? inputEl.value.substring(1, inputEl.value.length)
: inputEl.value.substring(2, inputEl.value.length);
this.searchColumn(lastColumn, minuteValueAgain, 'end');

break;
default:
const startIndex = inputEl.value.length - 4;
const newString = inputEl.value.substring(startIndex);

inputEl.value = newString;
this.selectMultiColumn();
break;
inputEl.value = newString;
value = newString;
}

this.multiColumnSearch(firstColumn, lastColumn, value);
};

/**
Expand Down
94 changes: 94 additions & 0 deletions core/src/components/picker/test/keyboard-entry/picker.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,100 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await expect(ionChange).toHaveReceivedEventDetail({ value: 12 });
await expect(column).toHaveJSProperty('value', 12);
});

test('should allow typing 22 in a column where the max value is 23 and not just set it to 2', async ({ page }) => {
await page.setContent(
`
<ion-picker>
<ion-picker-column></ion-picker-column>
</ion-picker>

<script>
const column = document.querySelector('ion-picker-column');
column.numericInput = true;
const items = [
{ text: '01', value: 1 },
{ text: '02', value: 2},
{ text: '20', value: 20 },
{ text: '21', value: 21 },
{ text: '22', value: 22 },
{ text: '23', value: 23 }
];

items.forEach((item) => {
const option = document.createElement('ion-picker-column-option');
option.value = item.value;
option.textContent = item.text;

column.appendChild(option);
});
</script>
`,
config
);

const column = page.locator('ion-picker-column');
const ionChange = await page.spyOnEvent('ionChange');
await column.evaluate((el: HTMLIonPickerColumnElement) => el.setFocus());

// Simulate typing '22'
await page.keyboard.press('Digit2');
await page.keyboard.press('Digit2');

// Ensure the column value is updated to 22
await expect(ionChange).toHaveReceivedEventDetail({ value: 22 });
await expect(column).toHaveJSProperty('value', 22);
});

test('should set value to 2 and not wait for another digit when max value is 12', async ({ page }) => {
await page.setContent(
`
<ion-picker>
<ion-picker-column></ion-picker-column>
</ion-picker>

<script>
const column = document.querySelector('ion-picker-column');
column.numericInput = true;
const items = [
{ text: '01', value: 1 },
{ text: '02', value: 2 },
{ text: '03', value: 3 },
{ text: '04', value: 4 },
{ text: '05', value: 5 },
{ text: '06', value: 6 },
{ text: '07', value: 7 },
{ text: '08', value: 8 },
{ text: '09', value: 9 },
{ text: '10', value: 10 },
{ text: '11', value: 11 },
{ text: '12', value: 12 }
];

items.forEach((item) => {
const option = document.createElement('ion-picker-column-option');
option.value = item.value;
option.textContent = item.text;

column.appendChild(option);
});
</script>
`,
config
);

const column = page.locator('ion-picker-column');
const ionChange = await page.spyOnEvent('ionChange');
await column.evaluate((el: HTMLIonPickerColumnElement) => el.setFocus());

// Simulate typing '2'
await page.keyboard.press('Digit2');

// Ensure the value is immediately set to 2
await expect(ionChange).toHaveReceivedEventDetail({ value: 2 });
await expect(column).toHaveJSProperty('value', 2);
});

test('pressing Enter should dismiss the keyboard', async ({ page }) => {
test.info().annotations.push({
type: 'issue',
Expand Down
Loading