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

Finish making InputControl et al. more controllable #40568

Merged

Conversation

stokesman
Copy link
Contributor

@stokesman stokesman commented Apr 23, 2022

What?

Builds upon #40518 to add the changes to RangeControl and UnitControl necessitated by the making InputControl controllable regardless of its focused state.

Why?

RangeControl needs changes is because it depends on its NumberControl being self-controlled while focused (i.e. ignoring the value prop it gets from RangeControl) so that when RangeControl actively constrains its state value it does not interfere with the entry of the value in the focused input.

I've not yet pinpointed why UnitControl requires a change but without it, the issue is that it calls onChange twice for a single change (from a blur event). That probably wouldn't have hurt a thing but it's nice that our unit tests caught this. UPDATE: It turns out this was an existing issue but our test that would catch it, wasn't doing so on trunk. The related changes from this PR have already been extracted/merged with #40796 so they are to be removed from this branch.

How?

  • Logic is added to RangeControl to temporarily keep entered values in its number input if they are out-of-range or non-numeric.
  • UnitControl stops calling onChange explicitly from mayUpdateUnit because apparently InputControl now does so on its own

Testing Instructions

Unit tests pass.
Try finding any differences manually testing RangeControl and UnitControl.

@stokesman stokesman added the [Feature] UI Components Impacts or related to the UI component system label Apr 23, 2022
@github-actions
Copy link

github-actions bot commented Apr 23, 2022

Size Change: +53 B (0%)

Total Size: 1.23 MB

Filename Size Change
build/components/index.min.js 223 kB +53 B (0%)
ℹ️ View Unchanged
Filename Size
build/a11y/index.min.js 993 B
build/annotations/index.min.js 2.77 kB
build/api-fetch/index.min.js 2.27 kB
build/autop/index.min.js 2.15 kB
build/blob/index.min.js 487 B
build/block-directory/index.min.js 6.51 kB
build/block-directory/style-rtl.css 1.01 kB
build/block-directory/style.css 1.01 kB
build/block-editor/default-editor-styles-rtl.css 378 B
build/block-editor/default-editor-styles.css 378 B
build/block-editor/index.min.js 150 kB
build/block-editor/style-rtl.css 15.1 kB
build/block-editor/style.css 15.1 kB
build/block-library/blocks/archives/editor-rtl.css 61 B
build/block-library/blocks/archives/editor.css 60 B
build/block-library/blocks/archives/style-rtl.css 65 B
build/block-library/blocks/archives/style.css 65 B
build/block-library/blocks/audio/editor-rtl.css 150 B
build/block-library/blocks/audio/editor.css 150 B
build/block-library/blocks/audio/style-rtl.css 111 B
build/block-library/blocks/audio/style.css 111 B
build/block-library/blocks/audio/theme-rtl.css 125 B
build/block-library/blocks/audio/theme.css 125 B
build/block-library/blocks/avatar/editor-rtl.css 116 B
build/block-library/blocks/avatar/editor.css 116 B
build/block-library/blocks/avatar/style-rtl.css 59 B
build/block-library/blocks/avatar/style.css 59 B
build/block-library/blocks/block/editor-rtl.css 161 B
build/block-library/blocks/block/editor.css 161 B
build/block-library/blocks/button/editor-rtl.css 445 B
build/block-library/blocks/button/editor.css 445 B
build/block-library/blocks/button/style-rtl.css 560 B
build/block-library/blocks/button/style.css 560 B
build/block-library/blocks/buttons/editor-rtl.css 292 B
build/block-library/blocks/buttons/editor.css 292 B
build/block-library/blocks/buttons/style-rtl.css 275 B
build/block-library/blocks/buttons/style.css 275 B
build/block-library/blocks/calendar/style-rtl.css 207 B
build/block-library/blocks/calendar/style.css 207 B
build/block-library/blocks/categories/editor-rtl.css 84 B
build/block-library/blocks/categories/editor.css 83 B
build/block-library/blocks/categories/style-rtl.css 79 B
build/block-library/blocks/categories/style.css 79 B
build/block-library/blocks/code/style-rtl.css 103 B
build/block-library/blocks/code/style.css 103 B
build/block-library/blocks/code/theme-rtl.css 124 B
build/block-library/blocks/code/theme.css 124 B
build/block-library/blocks/columns/editor-rtl.css 108 B
build/block-library/blocks/columns/editor.css 108 B
build/block-library/blocks/columns/style-rtl.css 406 B
build/block-library/blocks/columns/style.css 406 B
build/block-library/blocks/comment-author-avatar/editor-rtl.css 125 B
build/block-library/blocks/comment-author-avatar/editor.css 125 B
build/block-library/blocks/comment-template/style-rtl.css 127 B
build/block-library/blocks/comment-template/style.css 127 B
build/block-library/blocks/comments-pagination-numbers/editor-rtl.css 123 B
build/block-library/blocks/comments-pagination-numbers/editor.css 121 B
build/block-library/blocks/comments-pagination/editor-rtl.css 222 B
build/block-library/blocks/comments-pagination/editor.css 209 B
build/block-library/blocks/comments-pagination/style-rtl.css 235 B
build/block-library/blocks/comments-pagination/style.css 231 B
build/block-library/blocks/comments-query-loop/editor-rtl.css 95 B
build/block-library/blocks/comments-query-loop/editor.css 95 B
build/block-library/blocks/comments-title/editor-rtl.css 75 B
build/block-library/blocks/comments-title/editor.css 75 B
build/block-library/blocks/cover/editor-rtl.css 546 B
build/block-library/blocks/cover/editor.css 547 B
build/block-library/blocks/cover/style-rtl.css 1.56 kB
build/block-library/blocks/cover/style.css 1.56 kB
build/block-library/blocks/embed/editor-rtl.css 293 B
build/block-library/blocks/embed/editor.css 293 B
build/block-library/blocks/embed/style-rtl.css 417 B
build/block-library/blocks/embed/style.css 417 B
build/block-library/blocks/embed/theme-rtl.css 124 B
build/block-library/blocks/embed/theme.css 124 B
build/block-library/blocks/file/editor-rtl.css 300 B
build/block-library/blocks/file/editor.css 300 B
build/block-library/blocks/file/style-rtl.css 255 B
build/block-library/blocks/file/style.css 255 B
build/block-library/blocks/file/view.min.js 353 B
build/block-library/blocks/freeform/editor-rtl.css 2.44 kB
build/block-library/blocks/freeform/editor.css 2.44 kB
build/block-library/blocks/gallery/editor-rtl.css 961 B
build/block-library/blocks/gallery/editor.css 964 B
build/block-library/blocks/gallery/style-rtl.css 1.51 kB
build/block-library/blocks/gallery/style.css 1.51 kB
build/block-library/blocks/gallery/theme-rtl.css 122 B
build/block-library/blocks/gallery/theme.css 122 B
build/block-library/blocks/group/editor-rtl.css 333 B
build/block-library/blocks/group/editor.css 333 B
build/block-library/blocks/group/style-rtl.css 57 B
build/block-library/blocks/group/style.css 57 B
build/block-library/blocks/group/theme-rtl.css 78 B
build/block-library/blocks/group/theme.css 78 B
build/block-library/blocks/heading/style-rtl.css 76 B
build/block-library/blocks/heading/style.css 76 B
build/block-library/blocks/html/editor-rtl.css 332 B
build/block-library/blocks/html/editor.css 333 B
build/block-library/blocks/image/editor-rtl.css 731 B
build/block-library/blocks/image/editor.css 730 B
build/block-library/blocks/image/style-rtl.css 529 B
build/block-library/blocks/image/style.css 535 B
build/block-library/blocks/image/theme-rtl.css 124 B
build/block-library/blocks/image/theme.css 124 B
build/block-library/blocks/latest-comments/style-rtl.css 284 B
build/block-library/blocks/latest-comments/style.css 284 B
build/block-library/blocks/latest-posts/editor-rtl.css 199 B
build/block-library/blocks/latest-posts/editor.css 198 B
build/block-library/blocks/latest-posts/style-rtl.css 447 B
build/block-library/blocks/latest-posts/style.css 446 B
build/block-library/blocks/list/style-rtl.css 88 B
build/block-library/blocks/list/style.css 88 B
build/block-library/blocks/media-text/editor-rtl.css 266 B
build/block-library/blocks/media-text/editor.css 263 B
build/block-library/blocks/media-text/style-rtl.css 493 B
build/block-library/blocks/media-text/style.css 490 B
build/block-library/blocks/more/editor-rtl.css 431 B
build/block-library/blocks/more/editor.css 431 B
build/block-library/blocks/navigation-link/editor-rtl.css 708 B
build/block-library/blocks/navigation-link/editor.css 706 B
build/block-library/blocks/navigation-link/style-rtl.css 115 B
build/block-library/blocks/navigation-link/style.css 115 B
build/block-library/blocks/navigation-submenu/editor-rtl.css 299 B
build/block-library/blocks/navigation-submenu/editor.css 299 B
build/block-library/blocks/navigation-submenu/view.min.js 375 B
build/block-library/blocks/navigation/editor-rtl.css 2.03 kB
build/block-library/blocks/navigation/editor.css 2.04 kB
build/block-library/blocks/navigation/style-rtl.css 1.93 kB
build/block-library/blocks/navigation/style.css 1.92 kB
build/block-library/blocks/navigation/view-modal.min.js 2.65 kB
build/block-library/blocks/navigation/view.min.js 395 B
build/block-library/blocks/nextpage/editor-rtl.css 395 B
build/block-library/blocks/nextpage/editor.css 395 B
build/block-library/blocks/page-list/editor-rtl.css 363 B
build/block-library/blocks/page-list/editor.css 363 B
build/block-library/blocks/page-list/style-rtl.css 175 B
build/block-library/blocks/page-list/style.css 175 B
build/block-library/blocks/paragraph/editor-rtl.css 157 B
build/block-library/blocks/paragraph/editor.css 157 B
build/block-library/blocks/paragraph/style-rtl.css 260 B
build/block-library/blocks/paragraph/style.css 260 B
build/block-library/blocks/post-author/style-rtl.css 175 B
build/block-library/blocks/post-author/style.css 176 B
build/block-library/blocks/post-comments-form/style-rtl.css 521 B
build/block-library/blocks/post-comments-form/style.css 521 B
build/block-library/blocks/post-comments/style-rtl.css 521 B
build/block-library/blocks/post-comments/style.css 521 B
build/block-library/blocks/post-excerpt/editor-rtl.css 73 B
build/block-library/blocks/post-excerpt/editor.css 73 B
build/block-library/blocks/post-excerpt/style-rtl.css 69 B
build/block-library/blocks/post-excerpt/style.css 69 B
build/block-library/blocks/post-featured-image/editor-rtl.css 721 B
build/block-library/blocks/post-featured-image/editor.css 721 B
build/block-library/blocks/post-featured-image/style-rtl.css 153 B
build/block-library/blocks/post-featured-image/style.css 153 B
build/block-library/blocks/post-template/editor-rtl.css 99 B
build/block-library/blocks/post-template/editor.css 98 B
build/block-library/blocks/post-template/style-rtl.css 323 B
build/block-library/blocks/post-template/style.css 323 B
build/block-library/blocks/post-terms/style-rtl.css 73 B
build/block-library/blocks/post-terms/style.css 73 B
build/block-library/blocks/post-title/style-rtl.css 80 B
build/block-library/blocks/post-title/style.css 80 B
build/block-library/blocks/preformatted/style-rtl.css 103 B
build/block-library/blocks/preformatted/style.css 103 B
build/block-library/blocks/pullquote/editor-rtl.css 198 B
build/block-library/blocks/pullquote/editor.css 198 B
build/block-library/blocks/pullquote/style-rtl.css 370 B
build/block-library/blocks/pullquote/style.css 370 B
build/block-library/blocks/pullquote/theme-rtl.css 167 B
build/block-library/blocks/pullquote/theme.css 167 B
build/block-library/blocks/query-pagination-numbers/editor-rtl.css 122 B
build/block-library/blocks/query-pagination-numbers/editor.css 121 B
build/block-library/blocks/query-pagination/editor-rtl.css 221 B
build/block-library/blocks/query-pagination/editor.css 211 B
build/block-library/blocks/query-pagination/style-rtl.css 234 B
build/block-library/blocks/query-pagination/style.css 231 B
build/block-library/blocks/query/editor-rtl.css 369 B
build/block-library/blocks/query/editor.css 369 B
build/block-library/blocks/quote/style-rtl.css 213 B
build/block-library/blocks/quote/style.css 213 B
build/block-library/blocks/quote/theme-rtl.css 223 B
build/block-library/blocks/quote/theme.css 226 B
build/block-library/blocks/read-more/style-rtl.css 132 B
build/block-library/blocks/read-more/style.css 132 B
build/block-library/blocks/rss/editor-rtl.css 202 B
build/block-library/blocks/rss/editor.css 204 B
build/block-library/blocks/rss/style-rtl.css 289 B
build/block-library/blocks/rss/style.css 288 B
build/block-library/blocks/search/editor-rtl.css 165 B
build/block-library/blocks/search/editor.css 165 B
build/block-library/blocks/search/style-rtl.css 397 B
build/block-library/blocks/search/style.css 398 B
build/block-library/blocks/search/theme-rtl.css 64 B
build/block-library/blocks/search/theme.css 64 B
build/block-library/blocks/separator/editor-rtl.css 140 B
build/block-library/blocks/separator/editor.css 140 B
build/block-library/blocks/separator/style-rtl.css 233 B
build/block-library/blocks/separator/style.css 233 B
build/block-library/blocks/separator/theme-rtl.css 194 B
build/block-library/blocks/separator/theme.css 194 B
build/block-library/blocks/shortcode/editor-rtl.css 474 B
build/block-library/blocks/shortcode/editor.css 474 B
build/block-library/blocks/site-logo/editor-rtl.css 759 B
build/block-library/blocks/site-logo/editor.css 759 B
build/block-library/blocks/site-logo/style-rtl.css 181 B
build/block-library/blocks/site-logo/style.css 181 B
build/block-library/blocks/site-tagline/editor-rtl.css 86 B
build/block-library/blocks/site-tagline/editor.css 86 B
build/block-library/blocks/site-title/editor-rtl.css 84 B
build/block-library/blocks/site-title/editor.css 84 B
build/block-library/blocks/social-link/editor-rtl.css 177 B
build/block-library/blocks/social-link/editor.css 177 B
build/block-library/blocks/social-links/editor-rtl.css 674 B
build/block-library/blocks/social-links/editor.css 673 B
build/block-library/blocks/social-links/style-rtl.css 1.37 kB
build/block-library/blocks/social-links/style.css 1.36 kB
build/block-library/blocks/spacer/editor-rtl.css 332 B
build/block-library/blocks/spacer/editor.css 332 B
build/block-library/blocks/spacer/style-rtl.css 48 B
build/block-library/blocks/spacer/style.css 48 B
build/block-library/blocks/table/editor-rtl.css 504 B
build/block-library/blocks/table/editor.css 504 B
build/block-library/blocks/table/style-rtl.css 625 B
build/block-library/blocks/table/style.css 625 B
build/block-library/blocks/table/theme-rtl.css 188 B
build/block-library/blocks/table/theme.css 188 B
build/block-library/blocks/tag-cloud/style-rtl.css 226 B
build/block-library/blocks/tag-cloud/style.css 227 B
build/block-library/blocks/template-part/editor-rtl.css 149 B
build/block-library/blocks/template-part/editor.css 149 B
build/block-library/blocks/template-part/theme-rtl.css 101 B
build/block-library/blocks/template-part/theme.css 101 B
build/block-library/blocks/text-columns/editor-rtl.css 95 B
build/block-library/blocks/text-columns/editor.css 95 B
build/block-library/blocks/text-columns/style-rtl.css 166 B
build/block-library/blocks/text-columns/style.css 166 B
build/block-library/blocks/verse/style-rtl.css 87 B
build/block-library/blocks/verse/style.css 87 B
build/block-library/blocks/video/editor-rtl.css 571 B
build/block-library/blocks/video/editor.css 572 B
build/block-library/blocks/video/style-rtl.css 173 B
build/block-library/blocks/video/style.css 173 B
build/block-library/blocks/video/theme-rtl.css 124 B
build/block-library/blocks/video/theme.css 124 B
build/block-library/common-rtl.css 993 B
build/block-library/common.css 990 B
build/block-library/editor-rtl.css 10.2 kB
build/block-library/editor.css 10.2 kB
build/block-library/index.min.js 176 kB
build/block-library/reset-rtl.css 478 B
build/block-library/reset.css 478 B
build/block-library/style-rtl.css 11.5 kB
build/block-library/style.css 11.5 kB
build/block-library/theme-rtl.css 689 B
build/block-library/theme.css 694 B
build/block-serialization-default-parser/index.min.js 1.12 kB
build/block-serialization-spec-parser/index.min.js 2.83 kB
build/blocks/index.min.js 47 kB
build/components/style-rtl.css 15 kB
build/components/style.css 15 kB
build/compose/index.min.js 11.2 kB
build/core-data/index.min.js 14.5 kB
build/customize-widgets/index.min.js 11 kB
build/customize-widgets/style-rtl.css 1.39 kB
build/customize-widgets/style.css 1.39 kB
build/data-controls/index.min.js 663 B
build/data/index.min.js 8.65 kB
build/date/index.min.js 32 kB
build/deprecated/index.min.js 518 B
build/dom-ready/index.min.js 336 B
build/dom/index.min.js 4.58 kB
build/edit-navigation/index.min.js 15.8 kB
build/edit-navigation/style-rtl.css 4.05 kB
build/edit-navigation/style.css 4.05 kB
build/edit-post/classic-rtl.css 546 B
build/edit-post/classic.css 547 B
build/edit-post/index.min.js 30.1 kB
build/edit-post/style-rtl.css 7.11 kB
build/edit-post/style.css 7.11 kB
build/edit-site/index.min.js 47.5 kB
build/edit-site/style-rtl.css 7.95 kB
build/edit-site/style.css 7.93 kB
build/edit-widgets/index.min.js 16.3 kB
build/edit-widgets/style-rtl.css 4.41 kB
build/edit-widgets/style.css 4.4 kB
build/editor/index.min.js 38.5 kB
build/editor/style-rtl.css 3.71 kB
build/editor/style.css 3.71 kB
build/element/index.min.js 4.29 kB
build/escape-html/index.min.js 548 B
build/format-library/index.min.js 6.62 kB
build/format-library/style-rtl.css 571 B
build/format-library/style.css 571 B
build/hooks/index.min.js 1.66 kB
build/html-entities/index.min.js 454 B
build/i18n/index.min.js 3.79 kB
build/is-shallow-equal/index.min.js 535 B
build/keyboard-shortcuts/index.min.js 1.83 kB
build/keycodes/index.min.js 1.41 kB
build/list-reusable-blocks/index.min.js 1.75 kB
build/list-reusable-blocks/style-rtl.css 838 B
build/list-reusable-blocks/style.css 838 B
build/media-utils/index.min.js 2.94 kB
build/notices/index.min.js 957 B
build/nux/index.min.js 2.12 kB
build/nux/style-rtl.css 751 B
build/nux/style.css 749 B
build/plugins/index.min.js 1.98 kB
build/preferences/index.min.js 1.2 kB
build/primitives/index.min.js 949 B
build/priority-queue/index.min.js 628 B
build/react-i18n/index.min.js 704 B
build/react-refresh-entry/index.min.js 8.44 kB
build/react-refresh-runtime/index.min.js 7.31 kB
build/redux-routine/index.min.js 2.69 kB
build/reusable-blocks/index.min.js 2.24 kB
build/reusable-blocks/style-rtl.css 256 B
build/reusable-blocks/style.css 256 B
build/rich-text/index.min.js 11.2 kB
build/server-side-render/index.min.js 1.61 kB
build/shortcode/index.min.js 1.52 kB
build/token-list/index.min.js 668 B
build/url/index.min.js 1.99 kB
build/vendors/react-dom.min.js 38.5 kB
build/vendors/react.min.js 4.34 kB
build/viewport/index.min.js 1.08 kB
build/warning/index.min.js 280 B
build/widgets/index.min.js 7.21 kB
build/widgets/style-rtl.css 1.16 kB
build/widgets/style.css 1.16 kB
build/wordcount/index.min.js 1.07 kB

compressed-size-action

@stokesman stokesman marked this pull request as ready for review April 25, 2022 04:15
@stokesman stokesman marked this pull request as draft April 25, 2022 06:20
@stokesman stokesman marked this pull request as ready for review April 25, 2022 06:46
@ciampo
Copy link
Contributor

ciampo commented Apr 25, 2022

I just pushed a quick fix to make sure that the CI checks could complete and added a small comment to explain why the onChange callback isn't called explicitly in UnitControl's mayUpdateUnit function

@ciampo ciampo added [Type] Bug An existing feature does not function as intended [Package] Components /packages/components labels Apr 25, 2022
Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @stokesman for you work on this!

Overall the code changes all make sense to me, and so my idea would be to merge this ASAP into #40568 (cc @youknowriad ) so that we can all give a final look at the fix on that PR.

The good amount of unit tests that we have also reassures me that these changes should be working fine.

I spent some more time checking the components in Storybook, and noticed a regression in the behavior of the "Reset" button in RangeControl:

trunk:

range-control-reset-before.mp4

This PR:

range-control-reset-after.mp4

Basically, with the changes from this PR, the "Reset" button needs to be clicked twice before effectively resetting the value. Once this is ironed out, it would be good to add a unit test to RangeControl to avoid this regression in the future.

@ciampo ciampo requested a review from youknowriad April 25, 2022 12:07
@stokesman
Copy link
Contributor Author

stokesman commented May 2, 2022

So the remaining two RangeControl issues were resets to initialPosition and potential interference with value entry in the number input. I think everything is finally solved here. Here's bit more info on the latest commits.

3800d28, ff6e10a & e427a0e
These relate to fixing the interference with value entry in the number input. Because RangeControl sends out clamped values if out-of-range values are entered in the number input, it has to avoid updating the number input with the clamped value coming back in from props. It's tricky and I couldn't find a simpler way to go about it than to imperatively overwrite the update to temporarily keep the divergence in the DOM ff6e10a.

38c0158, 8930905 & cb00749
These relate to fixing the reset behavior. 38c0158 fixes the reset to initialPosition but turned out to do so only for the uncontrolled instances. I noticed the reset behavior for controlled instances was busted again but the unit tests would not fail unless a value was defined 8930905. The tricky part about resets is that undefined may be sent out through onChange and, if undefined comes back in from props, the component has to distinguish it as a reset as opposed to a uncontrolled context. It was going to require both an extra state and effect hook to do that while employing useControlledValue and I discovered that useControlledState wouldn't need any extra help so I switched to the latter cb00749. I'd be glad to elaborate on this if anyone is interested in more detail.

fa2723d
While exploring the edge case of non-numeric values being entered in the number input I found that the state syncing in InputControl could get stuck due to one of the layout effect hooks not running when focus changes. I included the focus state variable in the hooks dependencies.

Before fa2723d
input-control-stuck-state-from-blur.mp4
After
input-control-unstuck-state-from-blur.mp4

@ciampo
Copy link
Contributor

ciampo commented May 2, 2022

A patch applicable to trunk with the same changes to range-control’s unit tests to verify that they all pass.

I can confirm that those tests on RangeControl all pass on trunk. Curiously enough, I also applied that small change to UnitControl's unit tests on trunk, and somehow that is causing a failure:

Screenshot 2022-05-02 at 21 36 36

So the remaining two RangeControl issues were resets to initialPosition and potential interference with value entry in the number input. I think everything is finally solved here. Here's bit more info on the latest commits.

This is great to hear! It's quite late for me to have a look today, but I'll definitely do so tomorrow. Thank you once again for your efforts here 🙏

@stokesman
Copy link
Contributor Author

Curiously enough, I also applied that small change to UnitControl's unit tests on trunk, and somehow that is causing a failure:

Yes, it seems on trunk (and here) that suite is just somehow working and any little change is likely to make some other test fail. Notice that the test that failed was not the one with the change. I think it's because there are actually a few more places in that file with a missing await. Here’s one:


We could fix the others in this PR or if we did it in a separate PR I don't think it would even cause a hiccup here.

@@ -215,7 +215,7 @@ describe( 'UnitControl', () => {
expect( input.value ).toBe( '300px' );
expect( state ).toBe( 50 );

user.keyboard( '{Escape}' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change can be dropped after rebasing on top of trunk since it was included in #40790

Comment on lines -181 to -184
onChangeProp?.(
`${ validParsedQuantity ?? '' }${ validParsedUnit }`,
changeProps
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These changes have been extracted to #40796, and can therefore be removed from this PR once #40796 is merged (and this PR is rebased on top of trunk).

Copy link
Contributor

@ciampo ciampo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank for for the hard work and the thoughtful explanations, @stokesman !

Honestly, the complexity that this component has reached makes it very complicated to code review. I did my best, but there's the chance I may have still missed something. At least the core behavior should be verified by the unit tests that we have in place.

Once the last pending comment, feel free to merge this PR into #40518.

At that point, we will be able to:

(cc @youknowriad )

Comment on lines +36 to +44
useEffect( () => {
if ( ref.current && isDiverging.current ) {
const input = ref.current;
const entry = input.value;
const { defaultView } = input.ownerDocument;
defaultView.requestAnimationFrame( () => ( input.value = entry ) );
isDiverging.current = false;
}
}, [ value ] );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to add some inline comments here — eg. why are we saving the input's value, and reapplying it to the same input in the next rendering frame?

Copy link
Contributor Author

@stokesman stokesman May 10, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment in 085fd03 and then while double checking things while testing out the change that was to be 2b14b0f, I discovered the hook didn't need to handle NaN values so I made 29634d7. The issue (as non-critical as could be) was the first update after a NaN entry failing to re-sync the input:

range-control-stuck-one-update-after-NaN.mp4

I'd missed this before #40568 (comment) because I dragged the slider instead of clicking it once.

| ChangeEvent< HTMLInputElement >
| PointerEvent< HTMLInputElement >,
} );
}
}, [ state.value ] );
refEvent.current = null;
}, [ state.value, state.isDragging, isFocused ] );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not clear to me why we need to pass "isFocused" to this hook. For me, isFocused is more a "component" problem in the sense that if we want to trigger onChangeHandler when the component loses focus, we should just call commit (which will change the state.value and trigger the change handler.

Adding isFocused and state.isDragging here is a code smell to me and potentially something that will create hidden bugs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The additional dependencies were added only in the interest of executing refEvent.current = null not onChangeHandler. That's done as part of making sure the effect hook used to intake values doesn't dispatch (via reset in the base PR) more than necessary.

Without using the existence of the event to avoid some dispatches, there was an issue with FocalPointPicker. That component is surely doing it wrong but it seemed the shorter and more defensive path to reduce the dispatches.

I'm glad to take another look and see how it might be improved upon. I have some ideas.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed 2b14b0f, which removes the additional dependencies. The event ref (if it exists) is now cleared when the intake effect hook runs. This is much better. Thank you for the critique @youknowriad 🙇

@ciampo
Copy link
Contributor

ciampo commented May 10, 2022

Just popping by to say that I won't be around until Wednesday (18th), so feel free to merge this PR into #40518 whenever Riad agrees to. We can then all take that PR for a solid, deep test before merging into trunk

@@ -52,6 +52,14 @@ function inputControlStateReducer(
composedStateReducers: StateReducer
): StateReducer {
return ( state, action ) => {
// Updates state and returns early when there's no action type. These
// are controlled updates and need no exposure to additional reducers.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When does this happen (action without type)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be in what I'm calling the intake effect

useLayoutEffect( () => {
if (
initialState.value !== currentState.current.value &&
! currentState.current.isDirty
) {
dispatch( { value: initialState.value } );
}
if ( refEvent.current ) refEvent.current = null;
}, [ initialState.value ] );

That was dispatching via reset in the base PR but in looking for a way to distinguish the update from props I landed on making the bare dispatch with no action type. I'd considered implementing another action (or bringing back update that used to exist and was used only for this purpose (props coming in)) but I went with the bare dispatch because I thought it would make for a little less changes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd have just used another action but it's not important :)

Copy link
Contributor

@youknowriad youknowriad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be completely honest, I don't really understand all the changes here but I'm ok with merging both PRs to test properly.

@stokesman stokesman merged commit c8a8013 into fix/input-field-reset-behavior May 11, 2022
@stokesman stokesman deleted the fix/input-field-reset-behavior-moar branch May 11, 2022 14:48
@stokesman
Copy link
Contributor Author

Ideally there might have been less roundabout on the way to these changes. I'd much prefer that the review burden were less but I'm not doubtful of the result. Thanks again to all involved for the help!

stokesman added a commit that referenced this pull request May 17, 2022
* Update `RangeControl` to play nice with revised `InputControl`

* Update `UnitControl` to play nice with revised `InputControl`

* Restore controlled mode to `RangeControl`

* Add missing ;

* Add comment after deleting `onChange`

* Update test of `RangeControl` to also test controlled mode

* Remove separate onChange call from reset handling in `RangeControl`

* Refine RESET logic of `InputControl` reducer

* Simplify refined RESET logic of `InputControl` reducer

* Restore initial position of `RangeControl` when without value

* Differentiate state sync effect hooks by event existence

* Add and use type `SecondaryReducer`

* Cleanup legacy `event.perist()`

* Simplify update from props in reducer

Co-authored-by: Lena Morita <lena@jaguchi.com>

* Ensure event is cleared after drag actions

* Avoid declaration of potential unused variable

* Add more reset unit tests for `RangeControl`

* Run `RangeControl` unit test in both controlled/uncontrolled modes

* Make “keep invaid values” test async

* Prevent interference of value entry in number input

* Remove unused `floatClamp` function

* Fix reset to `initialPosition`

* Fix a couple tests for controlled `RangeControl`

* Fix `RangeControl` reset

* Ensure `InputControl`’s state syncing works after focus changes

* Comment

* Ignore NaN values in `useUnimpededRangedNumberEntry`

* Refine use of event existence in state syncing effect hooks

Co-authored-by: Marco Ciampini <marco.ciampo@gmail.com>
Co-authored-by: Lena Morita <lena@jaguchi.com>
stokesman added a commit that referenced this pull request May 29, 2022
* Update `RangeControl` to play nice with revised `InputControl`

* Update `UnitControl` to play nice with revised `InputControl`

* Restore controlled mode to `RangeControl`

* Add missing ;

* Add comment after deleting `onChange`

* Update test of `RangeControl` to also test controlled mode

* Remove separate onChange call from reset handling in `RangeControl`

* Refine RESET logic of `InputControl` reducer

* Simplify refined RESET logic of `InputControl` reducer

* Restore initial position of `RangeControl` when without value

* Differentiate state sync effect hooks by event existence

* Add and use type `SecondaryReducer`

* Cleanup legacy `event.perist()`

* Simplify update from props in reducer

Co-authored-by: Lena Morita <lena@jaguchi.com>

* Ensure event is cleared after drag actions

* Avoid declaration of potential unused variable

* Add more reset unit tests for `RangeControl`

* Run `RangeControl` unit test in both controlled/uncontrolled modes

* Make “keep invaid values” test async

* Prevent interference of value entry in number input

* Remove unused `floatClamp` function

* Fix reset to `initialPosition`

* Fix a couple tests for controlled `RangeControl`

* Fix `RangeControl` reset

* Ensure `InputControl`’s state syncing works after focus changes

* Comment

* Ignore NaN values in `useUnimpededRangedNumberEntry`

* Refine use of event existence in state syncing effect hooks

Co-authored-by: Marco Ciampini <marco.ciampo@gmail.com>
Co-authored-by: Lena Morita <lena@jaguchi.com>
stokesman added a commit that referenced this pull request May 29, 2022
stokesman added a commit to stokesman/gutenberg that referenced this pull request Nov 26, 2023
* Update `RangeControl` to play nice with revised `InputControl`

* Update `UnitControl` to play nice with revised `InputControl`

* Restore controlled mode to `RangeControl`

* Add missing ;

* Add comment after deleting `onChange`

* Update test of `RangeControl` to also test controlled mode

* Remove separate onChange call from reset handling in `RangeControl`

* Refine RESET logic of `InputControl` reducer

* Simplify refined RESET logic of `InputControl` reducer

* Restore initial position of `RangeControl` when without value

* Differentiate state sync effect hooks by event existence

* Add and use type `SecondaryReducer`

* Cleanup legacy `event.perist()`

* Simplify update from props in reducer

Co-authored-by: Lena Morita <lena@jaguchi.com>

* Ensure event is cleared after drag actions

* Avoid declaration of potential unused variable

* Add more reset unit tests for `RangeControl`

* Run `RangeControl` unit test in both controlled/uncontrolled modes

* Make “keep invaid values” test async

* Prevent interference of value entry in number input

* Remove unused `floatClamp` function

* Fix reset to `initialPosition`

* Fix a couple tests for controlled `RangeControl`

* Fix `RangeControl` reset

* Ensure `InputControl`’s state syncing works after focus changes

* Comment

* Ignore NaN values in `useUnimpededRangedNumberEntry`

* Refine use of event existence in state syncing effect hooks

Co-authored-by: Marco Ciampini <marco.ciampo@gmail.com>
Co-authored-by: Lena Morita <lena@jaguchi.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] UI Components Impacts or related to the UI component system [Package] Components /packages/components [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants