-
-
Notifications
You must be signed in to change notification settings - Fork 18
BL-15962 Fix empty input and backspace handling in SmallNumberPicker #7734
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,48 +1,98 @@ | ||
| import { css } from "@emotion/react"; | ||
| import * as React from "react"; | ||
| import { useState } from "react"; | ||
| import TextField from "@mui/material/TextField"; | ||
| import ReactToolTip from "react-tooltip"; | ||
| import "./smallNumberPicker.less"; | ||
|
|
||
| export interface INumberChooserProps { | ||
| maxLimit: number; // a valid result cannot be greater than this | ||
| minLimit?: number; // a valid result cannot be less than this | ||
| handleChange: (newNumber: number) => void; | ||
| onValidityChange?: (isValid: boolean) => void; // Notifies parent about validity changes | ||
| tooltip?: string; // caller should localize | ||
| } | ||
|
|
||
| /** | ||
| * A React component for selecting nonnegative integers within a specified range. | ||
| * Ensures that the input adheres to the constraints and provides immediate feedback for invalid input. | ||
| */ | ||
| export const SmallNumberPicker: React.FunctionComponent<INumberChooserProps> = ( | ||
| props: INumberChooserProps, | ||
| ) => { | ||
| const initialValue = props.minLimit === undefined ? 1 : props.minLimit; | ||
| const [chosenNumber, setChosenNumber] = useState(initialValue); | ||
| const minimumValue = props.minLimit ?? 0; | ||
| const initialValue = minimumValue; | ||
| const [displayValue, setDisplayValue] = useState(initialValue.toString()); | ||
| const [lastValidValue, setLastValidValue] = useState(initialValue); | ||
|
|
||
| // We have the input allow empty string so that the user can clear the input before entering a new number | ||
| // but don't persist or submit it | ||
| function isValid(input: HTMLInputElement): boolean { | ||
| return input.validity.valid && input.value !== ""; | ||
| } | ||
|
|
||
| const handleNumberChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||
| const newString = event.target.value; | ||
| const newNum = parseInt(newString); | ||
| const newNum = event.target.valueAsNumber; | ||
|
|
||
| // Don't allow typing in invalid characters; immediately snap back | ||
| // Except we don't prevent underflow immediately, so e.g. users can type digits "10" when the minimum is 2. | ||
| // Number inputs allow e for exponential notation but for a small number picker it only makes behavior more confusing | ||
| if ( | ||
| !newNum || | ||
| newNum > props.maxLimit || | ||
| (props.minLimit && newNum < props.minLimit) | ||
| event.target.validity.badInput || | ||
| event.target.validity.rangeOverflow || | ||
| newString.toLowerCase().includes("e") | ||
| ) { | ||
| setChosenNumber(initialValue); | ||
| props.handleChange(initialValue); | ||
| } else { | ||
| setChosenNumber(newNum); | ||
| return; | ||
| } | ||
|
Comment on lines
40
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Keystroke-level The Suggested approachReplace the Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
|
|
||
|
Comment on lines
+45
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 Info: The On line 32, the check Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| setDisplayValue(newString); | ||
|
|
||
| const valid = isValid(event.target); | ||
| props.onValidityChange?.(valid); | ||
|
|
||
| if (valid) { | ||
| setLastValidValue(newNum); | ||
| props.handleChange(newNum); | ||
| } | ||
| }; | ||
|
|
||
| // We would love to set the TextField "type" to "number", but this introduces up/down arrows that we | ||
| // can't get rid of in Firefox and have the input still perform as a number input. This means we have to | ||
| // use a "text" style input and handle max and min and letter input in code. Any invalid input sets the | ||
| // input value back to the 'minLimit'. | ||
| // If the user clicks away with the input empty or invalid, restore the last valid value | ||
| const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => { | ||
| const input = event.target; | ||
| if (!isValid(input)) { | ||
| setDisplayValue(lastValidValue.toString()); | ||
| props.onValidityChange?.(true); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
|
nabalone marked this conversation as resolved.
|
||
| <div className="smallNumberPicker"> | ||
| <div data-tip={props.tooltip}> | ||
| <TextField | ||
| css={css` | ||
| /* Don't display the little up/down arrows for number input */ | ||
| input[type="number"] { | ||
| -moz-appearance: textfield; | ||
| } | ||
| input[type="number"]::-webkit-outer-spin-button, | ||
| input[type="number"]::-webkit-inner-spin-button { | ||
| -webkit-appearance: none; | ||
| margin: 0; | ||
| } | ||
|
|
||
| input[type="number"] { | ||
| text-align: right; | ||
| } | ||
| `} | ||
| onBlur={handleBlur} | ||
| onChange={handleNumberChange} | ||
| value={chosenNumber} | ||
| value={displayValue} | ||
| type="number" | ||
| inputProps={{ | ||
| min: minimumValue, | ||
| max: props.maxLimit, | ||
| step: 1, | ||
| }} | ||
| variant="standard" | ||
| /> | ||
| </div> | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.