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

[Slider] Use percentage values in slider styles #2341

Merged
merged 11 commits into from
Apr 5, 2018

Conversation

reiv
Copy link
Contributor

@reiv reiv commented Mar 30, 2018

Fixes #2338

Changes proposed in this pull request:

This PR changes the slider styles to use percentages instead of absolute pixel values. This should allow the slider to be freely resized without requiring a forced re-render.

Screenshot

This recording shows the slider being resized (width is being set in the inspector).

2018-03-30_18-58-56

@llorca llorca requested a review from giladgray March 30, 2018 17:54
@llorca
Copy link
Contributor

llorca commented Mar 30, 2018

uh oh, that's great stuff. seems to be working well!

I'm seeing a little regression with the 3rd docs example: the slider handle is gone
image

@reiv
Copy link
Contributor Author

reiv commented Mar 30, 2018

Restore PEMDAS

Preview: documentation | landing | table

@giladgray
Copy link
Contributor

@reiv dude yes this is brilliant! do we even need tickSize (pixels) anymore?

@reiv
Copy link
Contributor Author

reiv commented Apr 3, 2018

@giladgray Had to leave that in because there was some stuff in the slider handle code (clientToValue) that depended on it:

// convert pixels to range value in increments of `stepSize`
const valueDelta = Math.round(pixelDelta / (tickSize * stepSize)) * stepSize;

Perhaps that can be rewritten too, but I don't fully understand it yet.

@reiv
Copy link
Contributor Author

reiv commented Apr 4, 2018

Unrelated to this issue, but it looks like there's a possible oversight in Handle.render():

const { handleMidpoint } = this.getHandleMidpointAndOffset(this.handleElement, true);
const offset = Math.round((value - min) * tickSize - handleMidpoint);
const style: React.CSSProperties = vertical ? { bottom: offset } : { left: offset };

private getHandleMidpointAndOffset(handleElement: HTMLElement, useOppositeDimension = false) {

I'm assuming getHandleMidpointAndOffset has a useOppositeDimension param to support getting the other midpoint of non-square handles (as it stands, both return 8, i.e. half of the handle's 16px side length). But in this case, we don't want the opposite dimension -- we'd get the vertical handle midpoint on a horizontal slider, resulting in this (handle height changed in inspector to exaggerate):

slider-midpoint

Was there ever a plan to permit controlling the handle size? Anyway, useOppositeDimension isn't going to affect anything as long as the handle is a square, but I'm going to go ahead and change this here just in case.

Edit: Hmm, looks like I misunderstood, and it was there for a very different reason: the range slider handle is special in that its midpoint is actually on the visible edge of the handle. But getHandleMidpointAndOffset doesn't take into account the 8px margin that's used to offset the midpoint, so measuring the other side happens to give the correct midpoint instead. I think this is still better than the alternative (using getComputedStyle() to include the margin); Going to add a comment for posterity though.

@reiv
Copy link
Contributor Author

reiv commented Apr 4, 2018

Restore previous logic with added comment

Preview: documentation | landing | table

Copy link
Contributor

@giladgray giladgray left a comment

Choose a reason for hiding this comment

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

@reiv this looks so great!! i'll look into removing tickSize after merging this. just one thought about numeric precision.

const offset = Math.round((value - min) * tickSize - handleMidpoint);
const style: React.CSSProperties = vertical ? { bottom: offset } : { left: offset };
const offsetRatio = (value - min) * tickSizeRatio;
const offsetCalc = `calc(${offsetRatio * 100}% - ${handleMidpoint}px)`;
Copy link
Contributor

Choose a reason for hiding this comment

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

👌

const initialValue = clamp(this.props.initialValue, this.props.min, this.props.max);

let offset = Math.round((initialValue - this.props.min) * tickSize);
let size = Math.round((this.props.value - initialValue) * tickSize);
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 cool that we don't need to round anymore, but i think we want to ensure a maximum number of decimal places via number.toFixed(2) to reduce sub-pixel rendering.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean rounding the value of offsetRatio? I don't think that would accomplish much because the actual computed value in pixels doesn't have any restrictions on the number of decimal places. Here's an example of a round percentage yielding sub-pixel precision (shows inline styles and computed value from inspector):

slider-subpixel

Unfortunately CSS calc provides no facilities for rounding the result. 😢

Copy link
Contributor

Choose a reason for hiding this comment

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

no i mean ${(offset * 100).toFixed(2)}%

Copy link
Contributor

Choose a reason for hiding this comment

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

sub-pixel renders are going to happen here for sure, but there's a limit to precision on the screen. it's definitely possible that my suggestion isn't really helpful?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well, at the very least we'll get inline styles that are easy on the eyes if anyone happens to be snooping around with inspect element. I'll make it happen 👍

@reiv
Copy link
Contributor Author

reiv commented Apr 5, 2018

@giladgray I believe tickSize is still needed because the handle code needs to know about the width of its container, for two reasons: to achieve "snapping" of the handle's offset to the correct tick position, and to convert the move events' offsets (as a ratio of the slider size) back to the represented values.

@reiv
Copy link
Contributor Author

reiv commented Apr 5, 2018

Renames

Preview: documentation | landing | table

@reiv
Copy link
Contributor Author

reiv commented Apr 5, 2018

Fix test

Preview: documentation | landing | table

const offsetPercentage = `${offset * 100}%`;
const sizePercentage = `${size * 100}%`;
const offsetPercentage = `${(offset * 100).toFixed(2)}%`;
const sizePercentage = `${(size * 100).toFixed(2)}%`;
Copy link
Contributor

Choose a reason for hiding this comment

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

ok well now we should definitely have a formatPercentage(size) utility function!

Copy link
Contributor

Choose a reason for hiding this comment

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

it could go in coreSlider.tsx

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok sure.

@@ -193,7 +193,7 @@ describe("<Slider>", () => {
const style = renderSlider(<Slider initialValue={-10} min={0} value={5} />)
.find("." + Classes.SLIDER_PROGRESS)
.prop("style") as React.CSSProperties;
assert.strictEqual(style.left, "0%");
assert.strictEqual(style.left, "0.00%");
Copy link
Contributor

Choose a reason for hiding this comment

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

yeah... always exactly 2 decimals.

@reiv
Copy link
Contributor Author

reiv commented Apr 5, 2018

Add formatPercentage helper function

Preview: documentation | landing | table

@reiv
Copy link
Contributor Author

reiv commented Apr 5, 2018

Renames

Preview: documentation | landing | table

Copy link
Contributor

@giladgray giladgray left a comment

Choose a reason for hiding this comment

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

hooray!

@giladgray giladgray merged commit f6562f5 into palantir:develop Apr 5, 2018
@reiv reiv deleted the rv/resizable-sider branch April 5, 2018 22:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants