Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: TimePicker – support replacement & removal by full selection #33344

Merged
merged 17 commits into from
Jan 2, 2024
Merged
Changes from 6 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
136 changes: 85 additions & 51 deletions src/components/TimePicker/TimePicker.js
paultsimura marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -114,27 +114,41 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) {
[hours, minute, amPmValue, defaultValue],
);

const resetHours = () => {
setHours('00');
setSelectionHour({start: 0, end: 0});
};

const resetMinutes = () => {
setMinute('00');
setSelectionMinute({start: 0, end: 0});
};

// This function receive value from hour input and validate it
// The valid format is HH(from 00 to 12). If the user input 9, it will be 09. If user try to change 09 to 19 it would skip the first character
const handleHourChange = (text) => {
paultsimura marked this conversation as resolved.
Show resolved Hide resolved
if (_.isEmpty(text)) {
resetHours();
return;
}

const isOnlyNumericValue = /^\d+$/.test(text.trim());
// Skip if the user is pasting the text or use non numeric characters.
if (selectionHour.start !== selectionHour.end || !isOnlyNumericValue) {
if (!isOnlyNumericValue) {
return;
}

// Remove non-numeric characters.
const filteredText = text.replace(/[^0-9]/g, '');

let newHour = hours;
let newSelection = selectionHour.start;

// Case when the cursor is at the start.
if (selectionHour.start === 0) {
// Handle cases where the hour would be > 12.

// when you entering text the filteredText would consist of three numbers
if (selectionHour.start === 0 && selectionHour.end === 0) {
// The cursor is at the start of hours
// When the user is entering text, the filteredText consists of three numbers
const formattedText = `${filteredText[0]}${filteredText[2] || 0}`;
if (formattedText > 12 && formattedText <= 24) {
// The hour is between 12 and 24 – switch AM to PM.
newHour = String(formattedText - 12).padStart(2, '0');
newSelection = 2;
setAmPmValue(CONST.TIME_PERIOD.PM);
Expand All @@ -145,35 +159,44 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) {
newHour = `${formattedText[0]}${formattedText[1]}`;
newSelection = 1;
}
} else if (selectionHour.start === 1) {
// Case when the cursor is at the second position.
} else if (selectionHour.start === 1 && selectionHour.end === 1) {
// The cursor is in-between the digits
const formattedText = `${filteredText[0]}${filteredText[1]}`;

if (filteredText.length < 2) {
// If we remove a value, prepend 0.
newHour = `0${text}`;
newHour = `0${filteredText}`;
newSelection = 0;
// If the second digit is > 2, replace the hour with 0 and the second digit.
} else if (formattedText > 12 && formattedText <= 24) {
newHour = String(formattedText - 12).padStart(2, '0');
newSelection = 2;
setAmPmValue(CONST.TIME_PERIOD.PM);
} else if (formattedText > 24) {
newHour = `0${text[1]}`;
newHour = `0${filteredText[1]}`;
newSelection = 2;
} else {
newHour = `${text[0]}${text[1]}`;
newHour = `${filteredText[0]}${filteredText[1]}`;
setHours(newHour);
newSelection = 2;
}
} else if (selectionHour.start === 2 && selectionHour.end === 2) {
// Case when the cursor is at the end and no text is selected.
if (filteredText.length < 2) {
newHour = `${text}0`;
newSelection = 1;
} else {
newSelection = 2;
}
} else if (filteredText.length <= 1 && filteredText < 2) {
/*
The filtered text is either 0 or 1. We must check the length of the filtered text to avoid incorrectly handling e.g. "01" as "1"
We are either replacing hours with a single digit, or removing the last digit.
In both cases, we should append 0 to the remaining value.
*/
newHour = `${filteredText}0`;
newSelection = 1;
} else if (filteredText > 12 && filteredText <= 24) {
// We are replacing hours with a value between 12 and 24. Switch AM to PM
newHour = String(filteredText - 12).padStart(2, '0');
newSelection = 2;
setAmPmValue(CONST.TIME_PERIOD.PM);
} else if (filteredText.length <= 2) {
// We are replacing hours with a value either 2-11, or 24+. Minimize the value to 12 and prepend 0 if needed
newHour = String(Math.min(filteredText, 12)).padStart(2, '0');
newSelection = 2;
}

setHours(newHour);
Expand All @@ -186,9 +209,13 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) {
// This function receive value from minute input and validate it
// The valid format is MM(from 00 to 59). If the user input 9, it will be 09. If user try to change 09 to 99 it would skip the character
const handleMinutesChange = (text) => {
if (_.isEmpty(text)) {
resetMinutes();
return;
}

const isOnlyNumericValue = /^\d+$/.test(text.trim());
// Skip if the user is pasting the text or use non numeric characters.
if (selectionMinute.start !== selectionMinute.end || !isOnlyNumericValue) {
if (!isOnlyNumericValue) {
return;
}

Expand All @@ -197,46 +224,41 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) {

let newMinute = minute;
let newSelection = selectionMinute.start;
// Case when user selects and replaces the text.
if (selectionMinute.start !== selectionMinute.end) {
// If the first digit is > 5, prepend 0.
if (filteredText.length === 1 && filteredText > 5) {
newMinute = `0${filteredText}`;
newSelection = 2;
// If the first digit is <= 5, append 0 at the end.
} else if (filteredText.length === 1 && filteredText <= 5) {
newMinute = `${filteredText}0`;
newSelection = 1;
} else {
newMinute = `${filteredText.slice(0, 2)}`;
newSelection = 2;
}
} else if (selectionMinute.start === 0) {
// Case when the cursor is at the start.

if (selectionMinute.start === 0 && selectionMinute.end === 0) {
// The cursor is at the start of minutes
const formattedText = `${filteredText[0]}${filteredText[2] || 0}`;
if (text[0] >= 6) {
if (filteredText[0] >= 6) {
newMinute = `0${formattedText[1]}`;
newSelection = 2;
} else {
newMinute = `${formattedText[0]}${formattedText[1]}`;
newSelection = 1;
}
} else if (selectionMinute.start === 1) {
// Case when the cursor is at the second position.
// If we remove a value, prepend 0.
} else if (selectionMinute.start === 1 && selectionMinute.end === 1) {
// The cursor is in-between the digits
if (filteredText.length < 2) {
newMinute = `0${text}`;
// If we remove a value, prepend 0.
newMinute = `0${filteredText}`;
newSelection = 0;
setSelectionHour({start: 2, end: 2});
hourInputRef.current.focus();
} else {
newMinute = `${text[0]}${text[1]}`;
newMinute = `${filteredText[0]}${filteredText[1]}`;
newSelection = 2;
}
} else if (filteredText.length < 2) {
// Case when the cursor is at the end and no text is selected.
newMinute = `${text}0`;
} else if (filteredText.length <= 1 && filteredText <= 5) {
/*
The filtered text is from 0 to 5. We must check the length of the filtered text to avoid incorrectly handling e.g. "01" as "1"
We are either replacing minutes with a single digit, or removing the last digit.
In both cases, we should append 0 to the remaining value.
*/
newMinute = `${filteredText}0`;
newSelection = 1;
} else if (filteredText.length <= 2) {
// We are replacing minutes with a value of 6+. Minimize the value to 59 and prepend 0 if needed
newMinute = String(Math.min(filteredText, 59)).padStart(2, '0');
newSelection = 2;
}

setMinute(newMinute);
Expand All @@ -262,14 +284,25 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) {
}
if (key === '<' || key === 'Backspace') {
if (isHourFocused) {
if (selectionHour.start === 0 && selectionHour.end === 2) {
resetHours();
return;
}

const newHour = replaceWithZeroAtPosition(hours, selectionHour.start);
setHours(newHour);
setSelectionHour(decreaseBothSelectionByOne(selectionHour));
} else if (isMinuteFocused) {
if (selectionMinute.start === 0) {
if (selectionMinute.start === 0 && selectionMinute.end === 2) {
resetMinutes();
return;
}

if (selectionMinute.start === 0 && selectionMinute.end === 0) {
focusHourInputOnLastCharacter();
return;
}

const newMinute = replaceWithZeroAtPosition(minute, selectionMinute.start);
setMinute(newMinute);
setSelectionMinute(decreaseBothSelectionByOne(selectionMinute));
Expand Down Expand Up @@ -322,13 +355,14 @@ function TimePicker({forwardedRef, defaultValue, onSubmit, onInputChange}) {

const handleFocusOnBackspace = useCallback(
(e) => {
if (selectionMinute.start !== 0 || e.key !== 'Backspace') {
if (selectionMinute.start !== 0 || selectionMinute.end !== 0 || e.key !== 'Backspace') {
return;
}
hourInputRef.current.focus();
e.preventDefault();
focusHourInputOnLastCharacter();
paultsimura marked this conversation as resolved.
Show resolved Hide resolved
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[selectionMinute.start],
[selectionMinute.start, selectionMinute.end, focusHourInputOnLastCharacter],
);

const {styleForAM, styleForPM} = StyleUtils.getStatusAMandPMButtonStyle(amPmValue);
Expand Down
Loading