-
-
Notifications
You must be signed in to change notification settings - Fork 145
Add optional numerical input box to dcc.Slider #944
base: dev
Are you sure you want to change the base?
Changes from 18 commits
c858dc9
6aeea78
d57bf70
7fc4de4
e24c3f5
796d30d
c5b7e51
168a99c
2e2dbe0
57936d4
205b9ed
3168a64
128017b
b555515
3e62912
3820ce4
884cccb
403ee9e
761103c
1709c21
2db424b
da7d116
4b45284
b7c3154
aa1ab08
0111a74
02c10c6
aebce8d
f445f58
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ import React, {Component} from 'react'; | |
import ReactSlider, {createSliderWithTooltip} from 'rc-slider'; | ||
import {assoc, omit, pickBy} from 'ramda'; | ||
import computeSliderStyle from '../utils/computeSliderStyle'; | ||
import Input from '../components/Input.react.js'; | ||
|
||
import 'rc-slider/assets/index.css'; | ||
|
||
|
@@ -16,8 +17,20 @@ export default class Slider extends Component { | |
this.DashSlider = props.tooltip | ||
? createSliderWithTooltip(ReactSlider) | ||
: ReactSlider; | ||
this.SyncedInput = Input; | ||
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. Probably don't need this indirection - 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. Actually, looking at this a little more closely, what's the benefit of using our 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. I agree, in hindsight it was confusing to re-use the |
||
this._computeStyle = computeSliderStyle(); | ||
this.state = {value: props.value}; | ||
this.syncInput = this.syncInput.bind(this); | ||
} | ||
|
||
syncInput(event) { | ||
if (event) { | ||
this.setState({value: Number(event.target.value)}); | ||
this.props.setProps({ | ||
value: Number(event.target.value), | ||
drag_value: Number(event.target.value), | ||
}); | ||
} | ||
} | ||
|
||
UNSAFE_componentWillReceiveProps(newProps) { | ||
|
@@ -47,6 +60,13 @@ export default class Slider extends Component { | |
setProps, | ||
tooltip, | ||
updatemode, | ||
syncedInput, | ||
syncedInputDebounceTime, | ||
syncedInputClassName, | ||
syncedInputStyle, | ||
syncedInputID, | ||
style, | ||
step, | ||
vertical, | ||
verticalHeight, | ||
} = this.props; | ||
|
@@ -72,15 +92,57 @@ export default class Slider extends Component { | |
) | ||
: this.props.marks; | ||
|
||
const computedStyle = this._computeStyle( | ||
vertical, | ||
verticalHeight, | ||
tooltip | ||
); | ||
|
||
const defaultInputStyle = { | ||
width: '60px', | ||
marginRight: vertical && syncedInput ? '' : '25px', | ||
marginBottom: vertical && syncedInput ? '25px' : '', | ||
}; | ||
|
||
return ( | ||
<div | ||
id={id} | ||
data-dash-is-loading={ | ||
(loading_state && loading_state.is_loading) || undefined | ||
} | ||
className={className} | ||
style={this._computeStyle(vertical, verticalHeight, tooltip)} | ||
style={{...computedStyle, ...style}} | ||
> | ||
{syncedInput ? ( | ||
<this.SyncedInput | ||
onChange={event => { | ||
event.persist(); | ||
if (this.timeout) { | ||
clearTimeout(this.timeout); | ||
} | ||
this.timeout = setTimeout( | ||
function() { | ||
this.syncInput(event); | ||
}.bind(this), | ||
syncedInputDebounceTime | ||
); | ||
}} | ||
onBlur={event => { | ||
this.syncInput(event); | ||
}} | ||
onKeyPress={event => { | ||
if (event.key === 'Enter') { | ||
this.syncInput(); | ||
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. We should probably clear the timeout here and in 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. wait, this particular call has no 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. This comment is was correct, but has been made outdated by refactoring this component to avoid needing to pass an |
||
} | ||
}} | ||
type="number" | ||
value={value} | ||
step={step} | ||
className={syncedInputClassName} | ||
id={syncedInputID} | ||
style={{...defaultInputStyle, ...syncedInputStyle}} | ||
/> | ||
) : null} | ||
<this.DashSlider | ||
onChange={value => { | ||
if (updatemode === 'drag') { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -156,6 +156,61 @@ def test_vertical_slider(self): | |
for entry in self.get_log(): | ||
raise Exception("browser error logged during test", entry) | ||
|
||
def test_horizontal_slider_with_input(self): | ||
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. This looks like a great test, but let's not add any more tests to the top-level files |
||
app = dash.Dash(__name__) | ||
|
||
app.layout = html.Div( | ||
[ | ||
html.Label("Horizontal Slider with Input"), | ||
dcc.Slider( | ||
id="horizontal-slider-with-input", | ||
min=0, | ||
max=9, | ||
value=5, | ||
syncedInputClassName="arbitraryClassName", | ||
syncedInput=True, | ||
), | ||
], | ||
style={"height": "500px"}, | ||
) | ||
self.startServer(app) | ||
|
||
self.wait_for_element_by_css_selector("#horizontal-slider-with-input") | ||
self.wait_for_element_by_css_selector(".arbitraryClassName") | ||
|
||
self.snapshot("horizontal slider with input") | ||
|
||
for entry in self.get_log(): | ||
raise Exception("browser error logged during test", entry) | ||
|
||
def test_vertical_slider_with_input(self): | ||
app = dash.Dash(__name__) | ||
|
||
app.layout = html.Div( | ||
[ | ||
html.Label("Vertical Slider with Input"), | ||
dcc.Slider( | ||
id="vertical-slider-with-input", | ||
min=0, | ||
max=9, | ||
value=5, | ||
vertical=True, | ||
syncedInputClassName="arbitraryClassName", | ||
syncedInput=True, | ||
), | ||
], | ||
style={"height": "500px"}, | ||
) | ||
self.startServer(app) | ||
|
||
self.wait_for_element_by_css_selector("#vertical-slider-with-input") | ||
self.wait_for_element_by_css_selector(".arbitraryClassName") | ||
|
||
self.snapshot("vertical slider with input") | ||
|
||
for entry in self.get_log(): | ||
raise Exception("browser error logged during test", entry) | ||
|
||
def test_loading_range_slider(self): | ||
lock = Lock() | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per the discussion @chriddyp and I had just now and summarized on Slack: let's convert all these new props to
snake_case
. In the short term this will make slider a little funny since it already has somecamelCase
props. That's OK, we'll get to it soon, and adding the backward-compatible conversion of other props is going to take some more work, especially since we useomit
in this component.