-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
SpinButton: Add onChange #16137
SpinButton: Add onChange #16137
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit a177fff:
|
Perf AnalysisNo significant results to display. All results
Perf Analysis (Fluent)Perf comparison
Perf tests with no regressions
|
Asset size changes
Baseline commit: 3ddffa4fb783da403f11ad1fc1496bb727dd0585 (build) |
Hello! I'm experiencing an issue that looks like onChange is incorrectly passed to a wrapper div. Sorry for the random comment, but I thought I'd drop a link here since this is the relevant PR that added onChange functionality to SpinButton. Issue details: #18153 |
Pull request checklist
$ yarn change
Description of changes
Finally add an
onChange
handler to SpinButton, with lots of tests.One important step before this was improving handling of intermediate values: only store "committed"/validated values in
value
, and add a new stateintermediateValue
(instead oflastValidValue
andvalueToValidate
) to store intermediate values as the user is typing in the field. You can see most of this part alone in 1ef1a36.I also updated theEDIT: this caused problems with Checkbox (and possibly other things) and I actually ended up solving the issue that motivated this change another way.useControllableValue
hook so that it only callsonChange
if the value actually changed. This appears to match React's behavior with input elements, and overall I'd guess it's more likely to match user expectations.Spec
This is the spec I followed for
onChange
behavior, based on the discussion on #5326 plus some updates to reflect actual implementation details and challenges (see #5326 (comment) for the original version).Intermediate vs. committed values
SpinButton can have two types of values: intermediate and committed.
A value is intermediate while the user is editing the text field.
The value is committed when any of the following happens:
The commit sequence consists of these steps:
onIncrement
,onDecrement
, oronValidate
as appropriatesetValue
returned byuseControllableValue
, which will:onChange
if providedintermediateValue
(causing the committed value to be displayed)Determining and storing the displayed value
Where is the value stored?
value
returned byuseControllableValue
(this will beprops.value
if controlled, or it tracks state if uncontrolled)intermediateValue
stateWhat is the initial value? (in order of precedence)
props.value
(no validation)defaultValue
(no validation)min
What is the currently displayed value?
intermediateValue
if set (while the user is editing text)value
returned byuseControllableValue
(this will beprops.value
if controlled)What is the value of
ISpinButton.value
?value
returned byuseControllableValue
(this will beprops.value
if controlled)intermediateValue
hereEdge cases while editing text:
props.value
we should retainintermediateValue
(remain in edit mode)props.value
, we should clearintermediateValue
(and exit edit mode without callingonChange
)How events are handled
Editing text: Update
intermediateValue
on each keystroke but do not callonChange
until commit.Blur or enter keypress (input field): Run the commit sequence starting with
onValidate
(onChange
will be called only if the value changed).Arrow button click or spin:
intermediateValue
is set), we have to allow the typed value to be committed before handling the increment/decrement:onBlur
runs, triggering the commit sequence (includingonChange
) and likely a value updateonIncrement
/onDecrement
(includingonChange
)Arrow key press or spin
intermediateValue
is set), we have to commit the typed value before handling the increment/decrement:onChange
), which will likely cause a value updateonIncrement
/onDecrement
(includingonChange
)Other design decisions
In some cases, the "correct" behavior was not entirely clear. Documenting some of those cases and the reasoning below.
props.value
orprops.defaultValue
(this is certainly correct forvalue
but debatable fordefaultValue
)onChange
is called for every spin, not only when spinning stops (no notion of intermediate values). We went with this because it's not uncommon that on every press, something in the app will react, like a font size going up and down.onChange
is not called for every keystroke when editing the text. We went with this because in this case, typing over the field seems more like hovering over options in a dropdown. If someone has a compelling need for notification of these intermediate values, we could consider adding something to handle that.ISpinButton.value
only returns the committed value, not the intermediate typed value. If someone has a compelling need for access to these intermediate values, we could consider adding something to handle that.props.value
is provided, the component is no longer fully controllable using just the return values ofonIncrement
/onDecrement
/onValidate
. This may be a breaking change for some people, though in most cases it could be mitigated by switching todefaultValue
.