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

RangeControl: Integrate NumberControl + update internal state flow #23006

Merged
merged 32 commits into from
Jun 26, 2020

Conversation

ItsJonQ
Copy link

@ItsJonQ ItsJonQ commented Jun 8, 2020

Screen Shot 2020-06-08 at 12 40 29 PM

(Screenshot demo'ing new track/rail color and <NumberControl /> integration)

This update improves the UI/UX, performance, and flexility of <RangeControl />. Performance has also been improved for components using the internal custom useControlledState hook, such as <InputControl />, <NumberControl />, <UnitControl />, and <BoxControl />.

🎚 RangeControl

🎨 Custom track and rail colors

<RangeControl /> is enhanced to render custom track and rail colors. This concept derived from an attempt to add filters to Cover backgrounds

83692175-7cd7a580-a5c1-11ea-9c0f-13afca87aa27

Notice in the demo GIF (above), the rail colours are using custom gradients to better demonstrate the values that's being adjusted - not unlike what you'd might find in Adobe Photoshop.

🔢 NumberControl

The previous input[type="number"] element has been replaced with a <NumberControl />. This unlocks some UX niceties such as Shift value jumping as well as drag to increment/decrement.

⏩ Jump Stepping

Screen Capture on 2020-06-05 at 16-03-22

Step values can now be "jumped" while pressing the arrow keys or dragging the <RangeControl /> while holding Shift. Rather than incrementing/decrementing values by step (default of 1), values can now be jumped (default of 10).

This provides a much nicer UX, especially for making quick adjustments.

This is achieved using the new custom useJumpStep hook. The behaviours are consistent with the jump stepping features in <NumberControl />

useJumpStep hook

This update adds a new custom hook - useJumpStep. This hook listens to Shift key presses on the window, and provides the appropriate step value used for "jumping" values.

For example, if the current value is 11. By default, when Shift is held down, incrementing the value will result in 20 rather than 12 (this is the jump!)

⚙️ useControlledState hook improvements

useControlledState hooks receives performance and stability updates. It does this by only managing internal state if the incoming value is not defined (is undefined or null). This helps better synchronize state/value updates across multiple (nested) components using useControlledState.

useControlledState has also been updated to handle initial and fallback values more gracefully (rather than the consuming component managing it).

Incoming/fallback values are important as different controlled values (not undefined) can result in different behaviour in HTML elements.

For example, for the input[type="range"] in <RangeControl />, we need a fallback value of null to center the slider. However, other input types (e.g. number or text) require an empty string ("").

👩‍🔬 How has this been tested?

  • Tested locally in Storybook
  • Tested in Jest
  • Tested in local Gutenberg

For testing...

  • Run npm run dev
  • Add a Cover block

Under the "Dimensions" panel...

  • Adjust the Cover height by entering in values
  • Adjust the Cover height by pressing up/down arrow keys (press Shift for jumping)
  • Change the unit value (e.g. px to em) and continue adjusting height value

Under the "Overlay" panel...

  • Drag the "background opacity" slider to update (press Shift for jumping)

  • Adjust the input field next to the slider (press Shift for jumping)

  • Add a Column block

Under the "Column settings" panel...

  • Drag the "Percentage width" slider to update (press Shift for jumping)
  • Adjust the input field next to the slider (press Shift for jumping)

Checklist:

  • My code is tested.
  • My code follows the WordPress code style.
  • My code follows the accessibility standards.
  • My code has proper inline documentation.
  • I've included developer documentation if appropriate.
  • I've updated all React Native files affected by any refactorings/renamings in this PR.

Related: #22278 (comment)

Jon Q added 16 commits June 5, 2020 11:00
Enhance RangeInput with track and rail props + style rendering.
Improve inline docs for rail/utils.js
The reason is because we're now relying on the native HTML `input[type="number"]` incrementing/decrementing function.
Jump stepping values (holding shift to jump by a multiplier, default of 10) is now being handled by the new useJumpStep hook. This hook listens to `Shift` keyboard presses and modifies the `step` prop that is passed into the `input[type="number"]` element.
@ItsJonQ ItsJonQ added [Package] Components /packages/components [Block] Cover Affects the Cover Block - used to display content laid over a background image labels Jun 8, 2020
@ItsJonQ ItsJonQ requested a review from youknowriad June 8, 2020 17:30
@ItsJonQ ItsJonQ self-assigned this Jun 8, 2020
@@ -34,20 +34,21 @@ const defaultInputProps = {
};

function useUniqueId( idProp ) {
const instanceId = useInstanceId( BoxControl );
const id = `inspector-box-control-${ instanceId }`;
const instanceId = useInstanceId( BoxControl, 'inspector-box-control' );
Copy link
Author

Choose a reason for hiding this comment

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

Refactored to use the prefix argument of useInstanceId

@github-actions
Copy link

github-actions bot commented Jun 8, 2020

Size Change: +732 B (0%)

Total Size: 1.13 MB

Filename Size Change
build/a11y/index.js 1.14 kB -1 B
build/annotations/index.js 3.62 kB +1 B
build/autop/index.js 2.82 kB +1 B
build/block-editor/index.js 109 kB +36 B (0%)
build/block-editor/style-rtl.css 10.7 kB +6 B (0%)
build/block-editor/style.css 10.7 kB +8 B (0%)
build/block-library/index.js 130 kB +6 B (0%)
build/block-serialization-default-parser/index.js 1.88 kB -1 B
build/blocks/index.js 48.2 kB +2 B (0%)
build/components/index.js 198 kB +675 B (0%)
build/compose/index.js 9.65 kB -1 B
build/core-data/index.js 11.4 kB +1 B
build/data/index.js 8.44 kB +2 B (0%)
build/dom/index.js 3.19 kB +1 B
build/edit-navigation/index.js 9.87 kB -1 B
build/edit-widgets/index.js 9.32 kB -2 B (0%)
build/editor/index.js 44.8 kB -3 B (0%)
build/element/index.js 4.65 kB +1 B
build/format-library/index.js 7.72 kB -2 B (0%)
build/is-shallow-equal/index.js 711 B +1 B
build/keyboard-shortcuts/index.js 2.51 kB +1 B
build/list-reusable-blocks/index.js 3.12 kB -4 B (0%)
build/plugins/index.js 2.56 kB +3 B (0%)
build/priority-queue/index.js 788 B -1 B
build/redux-routine/index.js 2.85 kB +3 B (0%)
build/server-side-render/index.js 2.68 kB +1 B
build/token-list/index.js 1.28 kB -1 B
ℹ️ View Unchanged
Filename Size Change
build/api-fetch/index.js 3.4 kB 0 B
build/blob/index.js 620 B 0 B
build/block-directory/index.js 7.37 kB 0 B
build/block-directory/style-rtl.css 941 B 0 B
build/block-directory/style.css 942 B 0 B
build/block-library/editor-rtl.css 7.59 kB 0 B
build/block-library/editor.css 7.6 kB 0 B
build/block-library/style-rtl.css 8.04 kB 0 B
build/block-library/style.css 8.04 kB 0 B
build/block-library/theme-rtl.css 730 B 0 B
build/block-library/theme.css 732 B 0 B
build/block-serialization-spec-parser/index.js 3.1 kB 0 B
build/components/style-rtl.css 15.9 kB 0 B
build/components/style.css 15.9 kB 0 B
build/data-controls/index.js 1.29 kB 0 B
build/date/index.js 5.47 kB 0 B
build/deprecated/index.js 772 B 0 B
build/dom-ready/index.js 569 B 0 B
build/edit-navigation/style-rtl.css 1.02 kB 0 B
build/edit-navigation/style.css 1.02 kB 0 B
build/edit-post/index.js 303 kB 0 B
build/edit-post/style-rtl.css 5.51 kB 0 B
build/edit-post/style.css 5.5 kB 0 B
build/edit-site/index.js 16.6 kB 0 B
build/edit-site/style-rtl.css 3.03 kB 0 B
build/edit-site/style.css 3.03 kB 0 B
build/edit-widgets/style-rtl.css 2.42 kB 0 B
build/edit-widgets/style.css 2.42 kB 0 B
build/editor/editor-styles-rtl.css 537 B 0 B
build/editor/editor-styles.css 539 B 0 B
build/editor/style-rtl.css 3.85 kB 0 B
build/editor/style.css 3.85 kB 0 B
build/escape-html/index.js 733 B 0 B
build/format-library/style-rtl.css 547 B 0 B
build/format-library/style.css 548 B 0 B
build/hooks/index.js 2.13 kB 0 B
build/html-entities/index.js 622 B 0 B
build/i18n/index.js 3.56 kB 0 B
build/keycodes/index.js 1.94 kB 0 B
build/list-reusable-blocks/style-rtl.css 450 B 0 B
build/list-reusable-blocks/style.css 451 B 0 B
build/media-utils/index.js 5.29 kB 0 B
build/notices/index.js 1.79 kB 0 B
build/nux/index.js 3.4 kB 0 B
build/nux/style-rtl.css 663 B 0 B
build/nux/style.css 660 B 0 B
build/primitives/index.js 1.5 kB 0 B
build/rich-text/index.js 14 kB 0 B
build/shortcode/index.js 1.7 kB 0 B
build/url/index.js 4.06 kB 0 B
build/viewport/index.js 1.85 kB 0 B
build/warning/index.js 1.14 kB 0 B
build/wordcount/index.js 1.17 kB 0 B

compressed-size-action

import {
inputControlActionTypes,
composeStateReducers,
} from '../input-control/state';
import { useRTL } from '../utils/style-mixins';
import { add, subtract, roundClamp } from '../utils/math';
Copy link
Author

Choose a reason for hiding this comment

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

Abstracted various functions it new dedicated utils/*.js files, which are shared amongst various components.

@@ -72,7 +76,7 @@ export function NumberControl(
const enableShift = event.shiftKey && isShiftStepEnabled;

const incrementalValue = enableShift
? parseFloat( shiftStep )
? parseFloat( shiftStep ) * parseFloat( step )
Copy link
Author

Choose a reason for hiding this comment

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

Improvement: The modifier now accounts for shiftStep and step (used as a multiplier)

Copy link
Member

@oandregal oandregal Jun 18, 2020

Choose a reason for hiding this comment

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

So, if I'm understanding this correctly, this change makes it so that:

  • user doesn't hold shift => values will increment by step (ex: 5) => 5 by 5
  • user holds shift => values will increment by shiftStep (ex: 10) * step (ex: 5) => 50 by 50

The actual variation that happens when the user holds down shift is always tied to the step, and shiftStep works as an accelerator. It makes sense to me, I can't think of any cases where using two unrelated values would make sense (like step = 3 without shift and step = 7 with shift).

However, I'm wondering if this is a breaking change. It seems to me like it is, given that consumers of this API will have to change the value they pass to shiftStep to get the same results when this lands. I guess then this would require the appropriate changelog note.

Copy link
Author

Choose a reason for hiding this comment

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

So, if I'm understanding this correctly, this change makes it so that:

@nosolosw Yes! You are correct. This became (more) evident to me when I was working with 0.01 step values. I expected the shift jump needed to be relative to the step value.

I guess then this would require the appropriate changelog note.

Happy to provide that!

Copy link
Member

Choose a reason for hiding this comment

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

In addition to the changelog, this component's README file also needs updating to explain the new relationship between step/shiftStep.

@@ -101,7 +105,9 @@ export function NumberControl(
if ( type === inputControlActionTypes.DRAG && isDragEnabled ) {
const { delta, shiftKey } = payload;
const [ x, y ] = delta;
const modifier = shiftKey ? shiftStep : 1;
const modifier = shiftKey
Copy link
Author

Choose a reason for hiding this comment

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

Improvement: The modifier now accounts for shiftStep and step (used as a multiplier)

- **Tab and arrow**: The slider is controlled by using the tab key to select the thumb of the desired slider, then using arrow keys to move it.
- **Value entry field**: Discrete sliders have value entry fields. After a text entry is made, the slider position automatically updates to reflect the new value.
- **Tick marks** (Optional) Discrete sliders can use evenly spaced tick marks along the slider track, and the thumb will snap to them. Each tick mark should change the setting in increments that are discernible to the user.
- **Click and drag**: The slider is controlled by clicking the thumb and dragging it.
Copy link
Author

Choose a reason for hiding this comment

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

A bunch of auto-formatting

Copy link
Member

Choose a reason for hiding this comment

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

is this an editor config? I don't mind as long as we're consistent within the file (ideally, also within the repo). I'd perhaps look into why these are added? it seems weird to have 3 spaces instead of 1 in between the - and the text.

Copy link
Author

Choose a reason for hiding this comment

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

@nosolosw I'm not sure? My VSCode auto formatted it like that. I have both Editorconfig and Prettier plugins installed.

type={ typeProp }
value={ initialValue }
value={ valueProp }
Copy link
Member

Choose a reason for hiding this comment

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

I've tracked the use of this by the sub-layers and I haven't seen any change to how it's dealt with. However, before this PR the value was normalized to a floating-point number. Would it be a problem that we don't do it now?

Copy link
Author

Choose a reason for hiding this comment

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

I just did a couple of tests.

With how the "pass-through" of props->state works now, normalizing the value is disruptive. This is applicable only when the type is text for this component. In the case of UnitControl, it uses this component underneath, but it takes care of normalizing values.

This is a little tricky to describe. Hopefully that makes sense!

In other words, it's currently structured where validation/normalization comes from the consumer, rather than the component itself. (Secretly, I'm hoping to enhance this in the future so that there's less work for consumers).

For now, I don't think there is a problem, based on currently use-cases and implementations.

Copy link
Member

@oandregal oandregal Jun 26, 2020

Choose a reason for hiding this comment

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

👍

So, if I'm an external (non-Gutenberg) consumer of this component, now I need to do the normalization myself, right? I guess this also needs a place in the changelog. As I mentioned in the other thread above, I'm unsure about what/how we log things in the changelog for this particular package, but IMHO, this should be logged as a breaking change.

Also: this needs to be added to the component's README.

@oandregal
Copy link
Member

Hey, tested this and it works as expected 👏

Code-wise, I'm not familiar with this part of the codebase plus it's quite a bunch to review, so I'm not comfortable approving until I understand the details and potential side-effects. I can resume my review in a few days. Hopefully, my brain has more short-term capacity by then and I'm able to navigate this quicker!

@ItsJonQ
Copy link
Author

ItsJonQ commented Jun 18, 2020

Thank you so much @nosolosw !!

Yes, this particular one is quite involved :). Thank you so much for taking the time to review it~! I really appreciate it

@ItsJonQ
Copy link
Author

ItsJonQ commented Jun 23, 2020

Yay! Travis is happy!

disabled = false,
help,
initialPosition,
isShiftStepEnabled = true,
Copy link
Member

Choose a reason for hiding this comment

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

I've seen a few changes in props here, so we should make sure docs are in sync. At least, the isShiftEnabled prop needs to be added.

aria-describedby={ describedBy }
aria-label={ label }
aria-hidden={ false }
className="components-range-control__slider"
Copy link
Member

Choose a reason for hiding this comment

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

I think the new InputRange component makes sense. However, I also feel that the components-range-control__slider class should be attached here, within the range component, and not in InputRange itself. The reason for this is that it's a class dependent on the parent component, and for homogeneity with the rest of child components.

Copy link
Author

Choose a reason for hiding this comment

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

I can make that change. For context, the component doesn't use classNames for styling. It maintains this className based selector targeting for consumers (and tests).

Copy link
Member

@oandregal oandregal left a comment

Choose a reason for hiding this comment

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

This works as expected for me and code changes make sense. I've left some minor comments in places, but they're not blocking.

The only things that we need to do are:

  • number-control: update readme & mark the breaking changes in the changelog
  • range-control: update readme with prop changes
  • input-range: add breaking change in changelog

@ItsJonQ
Copy link
Author

ItsJonQ commented Jun 26, 2020

Thank you @nosolosw ! I've applied the changes and pushed it up :)

@ItsJonQ
Copy link
Author

ItsJonQ commented Jun 26, 2020

Travis looks like it's happy! Will merge this up! Thanks @nosolosw !

@ItsJonQ ItsJonQ merged commit 4d5f19f into master Jun 26, 2020
@ItsJonQ ItsJonQ deleted the update/range-control-state-flow branch June 26, 2020 16:31
@github-actions github-actions bot added this to the Gutenberg 8.5 milestone Jun 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Block] Cover Affects the Cover Block - used to display content laid over a background image [Package] Components /packages/components [Type] Enhancement A suggestion for improvement. [Type] Performance Related to performance efforts
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants