From c858dc984835a3c2797adab3572fd8ab6b364780 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Thu, 25 Feb 2021 09:48:31 -0500 Subject: [PATCH 01/22] add input that shows slider value --- sample.py | 21 +++++++++++++++++++++ src/components/Slider.react.js | 5 +++++ src/fragments/Slider.react.js | 14 ++++++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 sample.py diff --git a/sample.py b/sample.py new file mode 100644 index 000000000..89b283643 --- /dev/null +++ b/sample.py @@ -0,0 +1,21 @@ +import dash +import dash_html_components as html +import dash_core_components as dcc + +external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] + +app = dash.Dash(__name__, external_stylesheets=external_stylesheets) +app.layout = html.Div([ + dcc.Slider( + id='my-slider', + min=0, + syncedInput=True, + max=20, + step=0.5, + value=10, + ), + html.Div(id='slider-output-container') +]) + +if __name__ == '__main__': + app.run_server(debug=True) diff --git a/src/components/Slider.react.js b/src/components/Slider.react.js index 116180dd6..8f6cf9906 100644 --- a/src/components/Slider.react.js +++ b/src/components/Slider.react.js @@ -119,6 +119,11 @@ Slider.propTypes = { */ step: PropTypes.number, + /** + * If true, the handles can't be moved. + */ + syncedInput: PropTypes.bool, + /** * If true, the slider will be vertical */ diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index 178014e39..73f397961 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -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'; @@ -47,6 +48,7 @@ export default class Slider extends Component { setProps, tooltip, updatemode, + syncedInput, vertical, verticalHeight, } = this.props; @@ -103,7 +105,7 @@ export default class Slider extends Component { ...tipProps, getTooltipContainer: node => node, }} - style={{position: 'relative'}} + style={{position: 'relative', float: 'left'}} value={value} marks={truncatedMarks} {...omit( @@ -118,7 +120,15 @@ export default class Slider extends Component { ], this.props )} - /> + > + + { + this.props.syncedInput ? ( + + ) : null + } ); } From 6aeea7886c8f922febd1cce12853bfcc16d1f9e2 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Thu, 4 Mar 2021 02:35:11 -0500 Subject: [PATCH 02/22] sync slider from input --- src/fragments/Slider.react.js | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index 73f397961..3eba1eb69 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -17,6 +17,7 @@ export default class Slider extends Component { this.DashSlider = props.tooltip ? createSliderWithTooltip(ReactSlider) : ReactSlider; + this.SyncedInput = Input this._computeStyle = computeSliderStyle(); this.state = {value: props.value}; } @@ -49,10 +50,12 @@ export default class Slider extends Component { tooltip, updatemode, syncedInput, + step, vertical, verticalHeight, } = this.props; const value = this.state.value; + let debounce = true; let tipProps; if (tooltip && tooltip.always_visible) { @@ -124,9 +127,25 @@ export default class Slider extends Component { { this.props.syncedInput ? ( - { + e.preventDefault() + }} + onBlur={e => { + this.setState({value: Number(e.target.value)}); + setProps({drag_value: Number(e.target.value)}); + }} + onKeyPress={e => { + if (e.key === 'Enter') { + this.setState({value: Number(e.target.value)}); + setProps({drag_value: Number(e.target.value)}); + } + }} + type="number" value={value} - /> + step={this.props.step} + > + ) : null } From d57bf70199c5f8320134643afbde92c92be3572d Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Thu, 4 Mar 2021 07:48:05 -0500 Subject: [PATCH 03/22] lint --- sample.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/sample.py b/sample.py index 89b283643..72e9a9970 100644 --- a/sample.py +++ b/sample.py @@ -2,20 +2,17 @@ import dash_html_components as html import dash_core_components as dcc -external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css'] +external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"] app = dash.Dash(__name__, external_stylesheets=external_stylesheets) -app.layout = html.Div([ - dcc.Slider( - id='my-slider', - min=0, - syncedInput=True, - max=20, - step=0.5, - value=10, - ), - html.Div(id='slider-output-container') -]) +app.layout = html.Div( + [ + dcc.Slider( + id="my-slider", min=0, syncedInput=True, max=20, step=0.5, value=10, + ), + html.Div(id="slider-output-container"), + ] +) -if __name__ == '__main__': +if __name__ == "__main__": app.run_server(debug=True) From 7fc4de4d73c92802da287511a989b82c3322c510 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Thu, 4 Mar 2021 08:47:06 -0500 Subject: [PATCH 04/22] fix lint errors --- src/fragments/Slider.react.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index 3eba1eb69..ffe0189c2 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -55,7 +55,6 @@ export default class Slider extends Component { verticalHeight, } = this.props; const value = this.state.value; - let debounce = true; let tipProps; if (tooltip && tooltip.always_visible) { @@ -126,7 +125,7 @@ export default class Slider extends Component { > { - this.props.syncedInput ? ( + syncedInput ? ( { e.preventDefault() @@ -143,7 +142,7 @@ export default class Slider extends Component { }} type="number" value={value} - step={this.props.step} + step={step} > ) : null From e24c3f50702b99619f0be67402b3105e91073ffa Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Thu, 4 Mar 2021 09:01:37 -0500 Subject: [PATCH 05/22] fix lint errors --- src/fragments/Slider.react.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index ffe0189c2..28eb4eee5 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -2,7 +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 Input from '../components/Input.react.js'; import 'rc-slider/assets/index.css'; @@ -17,7 +17,7 @@ export default class Slider extends Component { this.DashSlider = props.tooltip ? createSliderWithTooltip(ReactSlider) : ReactSlider; - this.SyncedInput = Input + this.SyncedInput = Input; this._computeStyle = computeSliderStyle(); this.state = {value: props.value}; } @@ -122,13 +122,11 @@ export default class Slider extends Component { ], this.props )} - > - - { - syncedInput ? ( - + {syncedInput ? ( + { - e.preventDefault() + e.preventDefault(); }} onBlur={e => { this.setState({value: Number(e.target.value)}); @@ -143,10 +141,8 @@ export default class Slider extends Component { type="number" value={value} step={step} - > - - ) : null - } + /> + ) : null} ); } From 796d30d3eb315e1bbfde5eb64d0368ec2b9177a5 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Sat, 6 Mar 2021 11:32:33 -0500 Subject: [PATCH 06/22] debounce and components on same line --- src/components/Slider.react.js | 2 +- src/fragments/Slider.react.js | 63 ++++++++++++++++++++++----------- src/utils/computeSliderStyle.js | 2 ++ 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/src/components/Slider.react.js b/src/components/Slider.react.js index 8f6cf9906..b4f48e078 100644 --- a/src/components/Slider.react.js +++ b/src/components/Slider.react.js @@ -120,7 +120,7 @@ Slider.propTypes = { step: PropTypes.number, /** - * If true, the handles can't be moved. + * If true, display an Input component whose value is synced with the Slider's value. */ syncedInput: PropTypes.bool, diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index 28eb4eee5..032d86f65 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -8,6 +8,8 @@ import 'rc-slider/assets/index.css'; import {propTypes, defaultProps} from '../components/Slider.react'; +const timeoutTime = 2000; + /** * A slider component with a single handle. */ @@ -85,6 +87,45 @@ export default class Slider extends Component { className={className} style={this._computeStyle(vertical, verticalHeight, tooltip)} > + {syncedInput ? ( + { + e.persist(); + if (this.timeout) { + clearTimeout(this.timeout); + } + this.timeout = setTimeout( + function() { + this.setState({ + value: Number(e.target.value), + }); + setProps({ + drag_value: Number(e.target.value), + }); + }.bind(this), + timeoutTime + ); + }} + onBlur={e => { + this.setState({value: Number(e.target.value)}); + setProps({drag_value: Number(e.target.value)}); + }} + onKeyPress={e => { + if (e.key === 'Enter') { + this.setState({value: Number(e.target.value)}); + setProps({drag_value: Number(e.target.value)}); + } + }} + type="number" + value={value} + step={step} + style={{ + width: '15%', + minWidth: '100px', + marginRight: '30px', + }} + /> + ) : null} { if (updatemode === 'drag') { @@ -107,7 +148,7 @@ export default class Slider extends Component { ...tipProps, getTooltipContainer: node => node, }} - style={{position: 'relative', float: 'left'}} + style={{position: 'relative', float: 'left', width: '85%'}} value={value} marks={truncatedMarks} {...omit( @@ -123,26 +164,6 @@ export default class Slider extends Component { this.props )} /> - {syncedInput ? ( - { - e.preventDefault(); - }} - onBlur={e => { - this.setState({value: Number(e.target.value)}); - setProps({drag_value: Number(e.target.value)}); - }} - onKeyPress={e => { - if (e.key === 'Enter') { - this.setState({value: Number(e.target.value)}); - setProps({drag_value: Number(e.target.value)}); - } - }} - type="number" - value={value} - step={step} - /> - ) : null} ); } diff --git a/src/utils/computeSliderStyle.js b/src/utils/computeSliderStyle.js index 5d6531311..47cf26aa1 100644 --- a/src/utils/computeSliderStyle.js +++ b/src/utils/computeSliderStyle.js @@ -4,6 +4,8 @@ export default () => { return memoizeWith(identity, (vertical, verticalHeight, tooltip) => { const style = { padding: '25px', + display: 'flex', + alignItems: 'center', }; if (vertical) { From c5b7e5156ceab78c72d9d0f4d1861ec4490d35e6 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Fri, 12 Mar 2021 16:21:10 -0500 Subject: [PATCH 07/22] set min width of synced input --- sample.py | 8 +++++++- src/fragments/Slider.react.js | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/sample.py b/sample.py index 72e9a9970..0882bac92 100644 --- a/sample.py +++ b/sample.py @@ -8,7 +8,13 @@ app.layout = html.Div( [ dcc.Slider( - id="my-slider", min=0, syncedInput=True, max=20, step=0.5, value=10, + id="my-slider", + min=0, + max=100, + syncedInput=True, + step=0.5, + value=10, + tooltip={"always_visible": True, "placement": "bottom"}, ), html.Div(id="slider-output-container"), ] diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index 032d86f65..f516c4955 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -148,7 +148,7 @@ export default class Slider extends Component { ...tipProps, getTooltipContainer: node => node, }} - style={{position: 'relative', float: 'left', width: '85%'}} + style={{position: 'relative', float: 'left', width: syncedInput ? '85%' : '100%'}} value={value} marks={truncatedMarks} {...omit( From 168a99cdbc042f872922246e8f6a12064958ebf2 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Thu, 18 Mar 2021 10:26:14 -0400 Subject: [PATCH 08/22] pass through props for synced input --- sample.py | 24 ------------- sample/assets/index.css | 14 ++++++++ sample/sample.py | 64 ++++++++++++++++++++++++++++++++++ src/components/Slider.react.js | 10 ++++++ src/fragments/Slider.react.js | 10 +++--- 5 files changed, 94 insertions(+), 28 deletions(-) delete mode 100644 sample.py create mode 100644 sample/assets/index.css create mode 100644 sample/sample.py diff --git a/sample.py b/sample.py deleted file mode 100644 index 0882bac92..000000000 --- a/sample.py +++ /dev/null @@ -1,24 +0,0 @@ -import dash -import dash_html_components as html -import dash_core_components as dcc - -external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"] - -app = dash.Dash(__name__, external_stylesheets=external_stylesheets) -app.layout = html.Div( - [ - dcc.Slider( - id="my-slider", - min=0, - max=100, - syncedInput=True, - step=0.5, - value=10, - tooltip={"always_visible": True, "placement": "bottom"}, - ), - html.Div(id="slider-output-container"), - ] -) - -if __name__ == "__main__": - app.run_server(debug=True) diff --git a/sample/assets/index.css b/sample/assets/index.css new file mode 100644 index 000000000..6bf0ad531 --- /dev/null +++ b/sample/assets/index.css @@ -0,0 +1,14 @@ +.syncedInput1{ + box-shadow: 5px 10px #888888; + min-width: 10% !important; +} + +.syncedInput2{ + box-shadow: 5px 10px #888888; + min-width: 20% !important; +} + +.syncedInput3{ + box-shadow: 5px 10px #888888; + width: 50px !important; +} diff --git a/sample/sample.py b/sample/sample.py new file mode 100644 index 000000000..4f6275d1e --- /dev/null +++ b/sample/sample.py @@ -0,0 +1,64 @@ +import dash +import dash_html_components as html +import dash_core_components as dcc + +external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"] + +app = dash.Dash(__name__) +app.layout = html.Div( + [ + dcc.Slider( + id="my-slider0", + min=0, + max=100, + step=0.5, + value=10, + ),dcc.Slider( + id="my-slider", + min=0, + max=100, + syncedInput=True, + step=0.5, + value=10, + tooltip={"always_visible": True, "placement": "bottom"}, + ), + dcc.Slider( + id="my-slider2", + min=0, + max=100000, + syncedInput=True, + syncedInputClassName="syncedInput1", + syncedInputDebounceTime=3500, + step=0.5, + value=10, + tooltip={"always_visible": True, "placement": "bottom"}, + ), + dcc.Slider( + id="my-slider3", + min=0, + max=10000000, + syncedInput=True, + syncedInputClassName="syncedInput2", + syncedInputDebounceTime=7500, + step=0.5, + value=10, + tooltip={"always_visible": True, "placement": "bottom"}, + ), + dcc.Slider( + id="my-slider4", + min=0, + max=10, + syncedInput=True, + syncedInputClassName="syncedInput3", + syncedInputDebounceTime=100, + step=0.5, + value=10, + vertical=True, + tooltip={"always_visible": True, "placement": "bottom"}, + ), + html.Div(id="slider-output-container"), + ] +) + +if __name__ == "__main__": + app.run_server(debug=True) diff --git a/src/components/Slider.react.js b/src/components/Slider.react.js index b4f48e078..3579517fa 100644 --- a/src/components/Slider.react.js +++ b/src/components/Slider.react.js @@ -124,6 +124,16 @@ Slider.propTypes = { */ syncedInput: PropTypes.bool, + /** + * The classname to be given to the synced Input component. + */ + syncedInputClassName: PropTypes.string, + + /** + * The amount of time the synced Input should wait before passing along state changes without a change of focus or the user pressing Enter. In milliseconds, default is 2000. + */ + syncedInputDebounceTime: PropTypes.number, + /** * If true, the slider will be vertical */ diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index f516c4955..2f9601aed 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -8,7 +8,7 @@ import 'rc-slider/assets/index.css'; import {propTypes, defaultProps} from '../components/Slider.react'; -const timeoutTime = 2000; +const defaultDebounceTimeout = 2000; /** * A slider component with a single handle. @@ -52,6 +52,8 @@ export default class Slider extends Component { tooltip, updatemode, syncedInput, + syncedInputClassName, + syncedInputDebounceTime, step, vertical, verticalHeight, @@ -103,7 +105,7 @@ export default class Slider extends Component { drag_value: Number(e.target.value), }); }.bind(this), - timeoutTime + syncedInputDebounceTime ? syncedInputDebounceTime : defaultDebounceTimeout ); }} onBlur={e => { @@ -119,9 +121,9 @@ export default class Slider extends Component { type="number" value={value} step={step} + className={syncedInputClassName} style={{ - width: '15%', - minWidth: '100px', + minWidth: '1%', marginRight: '30px', }} /> From 2e2dbe0fd8e0cfd5b314db81140b0fcce24062fb Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Mon, 29 Mar 2021 12:37:17 -0400 Subject: [PATCH 09/22] let user override default styles for input --- sample/assets/index.css | 14 -------- sample/sample.py | 64 --------------------------------- src/components/Slider.react.js | 15 ++++++-- src/fragments/Slider.react.js | 48 +++++++++++++------------ src/utils/computeSliderStyle.js | 3 +- 5 files changed, 40 insertions(+), 104 deletions(-) delete mode 100644 sample/assets/index.css delete mode 100644 sample/sample.py diff --git a/sample/assets/index.css b/sample/assets/index.css deleted file mode 100644 index 6bf0ad531..000000000 --- a/sample/assets/index.css +++ /dev/null @@ -1,14 +0,0 @@ -.syncedInput1{ - box-shadow: 5px 10px #888888; - min-width: 10% !important; -} - -.syncedInput2{ - box-shadow: 5px 10px #888888; - min-width: 20% !important; -} - -.syncedInput3{ - box-shadow: 5px 10px #888888; - width: 50px !important; -} diff --git a/sample/sample.py b/sample/sample.py deleted file mode 100644 index 4f6275d1e..000000000 --- a/sample/sample.py +++ /dev/null @@ -1,64 +0,0 @@ -import dash -import dash_html_components as html -import dash_core_components as dcc - -external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"] - -app = dash.Dash(__name__) -app.layout = html.Div( - [ - dcc.Slider( - id="my-slider0", - min=0, - max=100, - step=0.5, - value=10, - ),dcc.Slider( - id="my-slider", - min=0, - max=100, - syncedInput=True, - step=0.5, - value=10, - tooltip={"always_visible": True, "placement": "bottom"}, - ), - dcc.Slider( - id="my-slider2", - min=0, - max=100000, - syncedInput=True, - syncedInputClassName="syncedInput1", - syncedInputDebounceTime=3500, - step=0.5, - value=10, - tooltip={"always_visible": True, "placement": "bottom"}, - ), - dcc.Slider( - id="my-slider3", - min=0, - max=10000000, - syncedInput=True, - syncedInputClassName="syncedInput2", - syncedInputDebounceTime=7500, - step=0.5, - value=10, - tooltip={"always_visible": True, "placement": "bottom"}, - ), - dcc.Slider( - id="my-slider4", - min=0, - max=10, - syncedInput=True, - syncedInputClassName="syncedInput3", - syncedInputDebounceTime=100, - step=0.5, - value=10, - vertical=True, - tooltip={"always_visible": True, "placement": "bottom"}, - ), - html.Div(id="slider-output-container"), - ] -) - -if __name__ == "__main__": - app.run_server(debug=True) diff --git a/src/components/Slider.react.js b/src/components/Slider.react.js index 3579517fa..ac24335a1 100644 --- a/src/components/Slider.react.js +++ b/src/components/Slider.react.js @@ -122,7 +122,7 @@ Slider.propTypes = { /** * If true, display an Input component whose value is synced with the Slider's value. */ - syncedInput: PropTypes.bool, + syncedInput: PropTypes.bool, /** * The classname to be given to the synced Input component. @@ -130,7 +130,17 @@ Slider.propTypes = { syncedInputClassName: PropTypes.string, /** - * The amount of time the synced Input should wait before passing along state changes without a change of focus or the user pressing Enter. In milliseconds, default is 2000. + * The CSS to be applied to the class of the input (div) + */ + syncedInputStyle: PropTypes.object, + + /** + * The CSS to be applied to the class of the slider (div) + */ + style: PropTypes.object, + + /** + * The amount of time the synced Input should wait before passing along state changes without a change of focus or the user pressing Enter. In milliseconds. */ syncedInputDebounceTime: PropTypes.number, @@ -214,6 +224,7 @@ Slider.defaultProps = { persisted_props: ['value'], persistence_type: 'local', verticalHeight: 400, + syncedInputDebounceTime: 2000 }; export const propTypes = Slider.propTypes; diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index 2f9601aed..35774b82f 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -8,7 +8,6 @@ import 'rc-slider/assets/index.css'; import {propTypes, defaultProps} from '../components/Slider.react'; -const defaultDebounceTimeout = 2000; /** * A slider component with a single handle. @@ -22,6 +21,12 @@ export default class Slider extends Component { this.SyncedInput = Input; this._computeStyle = computeSliderStyle(); this.state = {value: props.value}; + this.updatePropsAndState = this.updatePropsAndState.bind(this) + } + + updatePropsAndState(e){ + this.setState({value: Number(e.target.value)}); + this.setProps({drag_value: Number(e.target.value)}); } UNSAFE_componentWillReceiveProps(newProps) { @@ -52,8 +57,10 @@ export default class Slider extends Component { tooltip, updatemode, syncedInput, - syncedInputClassName, syncedInputDebounceTime, + syncedInputClassName, + syncedInputStyle, + style, step, vertical, verticalHeight, @@ -80,6 +87,14 @@ export default class Slider extends Component { ) : this.props.marks; + const computedStyle = this._computeStyle(vertical, verticalHeight, tooltip); + + const defaultInputStyle = { + width: '60px', + marginRight: vertical ? '' : '25px', + marginBottom: vertical ? '25px' : '', + }; + return (
{syncedInput ? ( { + onChange={(e) => { e.persist(); if (this.timeout) { clearTimeout(this.timeout); } this.timeout = setTimeout( function() { - this.setState({ - value: Number(e.target.value), - }); - setProps({ - drag_value: Number(e.target.value), - }); - }.bind(this), - syncedInputDebounceTime ? syncedInputDebounceTime : defaultDebounceTimeout + this.updatePropsAndState()}.bind(this), + syncedInputDebounceTime ); }} - onBlur={e => { - this.setState({value: Number(e.target.value)}); - setProps({drag_value: Number(e.target.value)}); - }} - onKeyPress={e => { + onBlur={(e) => {this.updatePropsAndState(e)}} + onKeyPress={(e) => { if (e.key === 'Enter') { - this.setState({value: Number(e.target.value)}); - setProps({drag_value: Number(e.target.value)}); + this.updatePropsAndState(e) } }} type="number" value={value} step={step} className={syncedInputClassName} - style={{ - minWidth: '1%', - marginRight: '30px', - }} + style={{...defaultInputStyle, ...syncedInputStyle}} /> ) : null} { const style = { padding: '25px', display: 'flex', - alignItems: 'center', }; if (vertical) { style.height = verticalHeight + 'px'; + style.flexDirection = "column"; if ( !tooltip || @@ -30,6 +30,7 @@ export default () => { ) { style.paddingTop = '0px'; } + style.alignItems = 'center'; } return style; From 57936d4853cfb454991152b75b2d5f9ff72a0fa9 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Mon, 29 Mar 2021 14:07:54 -0400 Subject: [PATCH 10/22] run linter --- src/components/Slider.react.js | 18 +++++++-------- src/fragments/Slider.react.js | 39 +++++++++++++++++++++++---------- src/utils/computeSliderStyle.js | 2 +- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/components/Slider.react.js b/src/components/Slider.react.js index ac24335a1..097a65222 100644 --- a/src/components/Slider.react.js +++ b/src/components/Slider.react.js @@ -122,27 +122,27 @@ Slider.propTypes = { /** * If true, display an Input component whose value is synced with the Slider's value. */ - syncedInput: PropTypes.bool, + syncedInput: PropTypes.bool, /** * The classname to be given to the synced Input component. */ - syncedInputClassName: PropTypes.string, + syncedInputClassName: PropTypes.string, - /** + /** * The CSS to be applied to the class of the input (div) */ - syncedInputStyle: PropTypes.object, + syncedInputStyle: PropTypes.object, - /** + /** * The CSS to be applied to the class of the slider (div) */ - style: PropTypes.object, + style: PropTypes.object, - /** + /** * The amount of time the synced Input should wait before passing along state changes without a change of focus or the user pressing Enter. In milliseconds. */ - syncedInputDebounceTime: PropTypes.number, + syncedInputDebounceTime: PropTypes.number, /** * If true, the slider will be vertical @@ -224,7 +224,7 @@ Slider.defaultProps = { persisted_props: ['value'], persistence_type: 'local', verticalHeight: 400, - syncedInputDebounceTime: 2000 + syncedInputDebounceTime: 2000, }; export const propTypes = Slider.propTypes; diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index 35774b82f..e6757526b 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -8,7 +8,6 @@ import 'rc-slider/assets/index.css'; import {propTypes, defaultProps} from '../components/Slider.react'; - /** * A slider component with a single handle. */ @@ -21,12 +20,12 @@ export default class Slider extends Component { this.SyncedInput = Input; this._computeStyle = computeSliderStyle(); this.state = {value: props.value}; - this.updatePropsAndState = this.updatePropsAndState.bind(this) + this.updatePropsAndState = this.updatePropsAndState.bind(this); } - updatePropsAndState(e){ + updatePropsAndState(e) { this.setState({value: Number(e.target.value)}); - this.setProps({drag_value: Number(e.target.value)}); + this.props.setProps({drag_value: Number(e.target.value)}); } UNSAFE_componentWillReceiveProps(newProps) { @@ -87,7 +86,11 @@ export default class Slider extends Component { ) : this.props.marks; - const computedStyle = this._computeStyle(vertical, verticalHeight, tooltip); + const computedStyle = this._computeStyle( + vertical, + verticalHeight, + tooltip + ); const defaultInputStyle = { width: '60px', @@ -106,21 +109,29 @@ export default class Slider extends Component { > {syncedInput ? ( { + onChange={e => { e.persist(); if (this.timeout) { clearTimeout(this.timeout); } this.timeout = setTimeout( function() { - this.updatePropsAndState()}.bind(this), - syncedInputDebounceTime + this.setState({ + value: Number(e.target.value), + }); + setProps({ + drag_value: Number(e.target.value), + }); + }.bind(this), + syncedInputDebounceTime ); }} - onBlur={(e) => {this.updatePropsAndState(e)}} - onKeyPress={(e) => { + onBlur={e => { + this.updatePropsAndState(e); + }} + onKeyPress={e => { if (e.key === 'Enter') { - this.updatePropsAndState(e) + this.updatePropsAndState(e); } }} type="number" @@ -152,7 +163,11 @@ export default class Slider extends Component { ...tipProps, getTooltipContainer: node => node, }} - style={{position: 'relative', float: 'left', width: syncedInput ? '85%' : '100%'}} + style={{ + position: 'relative', + float: 'left', + width: syncedInput ? '85%' : '100%', + }} value={value} marks={truncatedMarks} {...omit( diff --git a/src/utils/computeSliderStyle.js b/src/utils/computeSliderStyle.js index 4a6540bd1..eec3c72ef 100644 --- a/src/utils/computeSliderStyle.js +++ b/src/utils/computeSliderStyle.js @@ -9,7 +9,7 @@ export default () => { if (vertical) { style.height = verticalHeight + 'px'; - style.flexDirection = "column"; + style.flexDirection = 'column'; if ( !tooltip || From 205b9ed38e65110a042a86e46922fa26b1addbc5 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Thu, 1 Apr 2021 07:52:48 -0400 Subject: [PATCH 11/22] set proper props for input --- src/components/Slider.react.js | 2 +- src/fragments/Slider.react.js | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/components/Slider.react.js b/src/components/Slider.react.js index 097a65222..96ec6e872 100644 --- a/src/components/Slider.react.js +++ b/src/components/Slider.react.js @@ -224,7 +224,7 @@ Slider.defaultProps = { persisted_props: ['value'], persistence_type: 'local', verticalHeight: 400, - syncedInputDebounceTime: 2000, + syncedInputDebounceTime: 450, }; export const propTypes = Slider.propTypes; diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index e6757526b..bb46d556d 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -116,22 +116,20 @@ export default class Slider extends Component { } this.timeout = setTimeout( function() { - this.setState({ - value: Number(e.target.value), - }); - setProps({ - drag_value: Number(e.target.value), - }); + this.setState({value: Number(e.target.value)}); + setProps({value: Number(e.target.value), drag_value: Number(e.target.value)}); }.bind(this), syncedInputDebounceTime ); }} onBlur={e => { - this.updatePropsAndState(e); + this.setState({value: Number(e.target.value)}); + setProps({value: Number(e.target.value), drag_value: Number(e.target.value)}); }} onKeyPress={e => { if (e.key === 'Enter') { - this.updatePropsAndState(e); + this.setState({value: Number(e.target.value)}); + setProps({value: Number(e.target.value), drag_value: Number(e.target.value)}); } }} type="number" From 3168a646a7545b7993390dbbe010e9aaa38326d8 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Thu, 1 Apr 2021 13:04:39 -0400 Subject: [PATCH 12/22] refactor input state/props sync --- src/fragments/Slider.react.js | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index bb46d556d..c471bc4f4 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -20,12 +20,14 @@ export default class Slider extends Component { this.SyncedInput = Input; this._computeStyle = computeSliderStyle(); this.state = {value: props.value}; - this.updatePropsAndState = this.updatePropsAndState.bind(this); + this.syncInput = this.syncInput.bind(this); } - updatePropsAndState(e) { - this.setState({value: Number(e.target.value)}); - this.props.setProps({drag_value: Number(e.target.value)}); + 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) { @@ -109,27 +111,22 @@ export default class Slider extends Component { > {syncedInput ? ( { - e.persist(); + onChange={event => { + event.persist(); if (this.timeout) { clearTimeout(this.timeout); } this.timeout = setTimeout( - function() { - this.setState({value: Number(e.target.value)}); - setProps({value: Number(e.target.value), drag_value: Number(e.target.value)}); - }.bind(this), + this.syncInput(event), syncedInputDebounceTime ); }} - onBlur={e => { - this.setState({value: Number(e.target.value)}); - setProps({value: Number(e.target.value), drag_value: Number(e.target.value)}); + onBlur={event => { + this.syncInput(event) }} - onKeyPress={e => { - if (e.key === 'Enter') { - this.setState({value: Number(e.target.value)}); - setProps({value: Number(e.target.value), drag_value: Number(e.target.value)}); + onKeyPress={event => { + if (event.key === 'Enter') { + this.syncInput() } }} type="number" From 128017b5f5fe0ea6dae7e4973756985c9e2a5a20 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Mon, 5 Apr 2021 20:27:38 -0400 Subject: [PATCH 13/22] update tests --- src/components/Slider.react.js | 6 ++ src/fragments/Slider.react.js | 2 + tests/integration/sliders/test_sliders.py | 96 +++++++++++++++++++++++ tests/test_integration_1.py | 61 ++++++++++++++ 4 files changed, 165 insertions(+) diff --git a/src/components/Slider.react.js b/src/components/Slider.react.js index 96ec6e872..defceb4c6 100644 --- a/src/components/Slider.react.js +++ b/src/components/Slider.react.js @@ -134,6 +134,11 @@ Slider.propTypes = { */ syncedInputStyle: PropTypes.object, + /** + * The id to be applied to the input (div). Default is "syncedInput". + */ + syncedInputID: PropTypes.string, + /** * The CSS to be applied to the class of the slider (div) */ @@ -225,6 +230,7 @@ Slider.defaultProps = { persistence_type: 'local', verticalHeight: 400, syncedInputDebounceTime: 450, + syncedInputID: "syncedInput" }; export const propTypes = Slider.propTypes; diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index c471bc4f4..dc36285a7 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -61,6 +61,7 @@ export default class Slider extends Component { syncedInputDebounceTime, syncedInputClassName, syncedInputStyle, + syncedInputID, style, step, vertical, @@ -133,6 +134,7 @@ export default class Slider extends Component { value={value} step={step} className={syncedInputClassName} + id={syncedInputID} style={{...defaultInputStyle, ...syncedInputStyle}} /> ) : null} diff --git a/tests/integration/sliders/test_sliders.py b/tests/integration/sliders/test_sliders.py index 09ab15963..a4ef3ee18 100644 --- a/tests/integration/sliders/test_sliders.py +++ b/tests/integration/sliders/test_sliders.py @@ -241,3 +241,99 @@ def update_output(value): dash_dcc.wait_for_text_to_equal("#out-value", "You have selected 5-15") dash_dcc.release() dash_dcc.wait_for_text_to_equal("#out-value", "You have selected 10-15") + +def test_slsl008_horizontal_slider_with_input(dash_dcc): + app = dash.Dash(__name__) + app.layout = html.Div( + [ + dcc.Slider( + id="slider", + min=0, + max=20, + step=1, + value=5, + syncedInput=True, + syncedInputID="syncedInput", + tooltip={"always_visible": True}, + ), + html.Div(id="out"), + ] + ) + + @app.callback(Output("out", "children"), [Input("slider", "value")]) + def update_output(value): + return "You have selected {}".format(value) + + dash_dcc.start_server(app) + dash_dcc.wait_for_text_to_equal("#out", "You have selected 5") + + input_ = dash_dcc.find_element("#syncedInput") + + input_.clear() + input_.send_keys("8") + + dash_dcc.wait_for_text_to_equal("#out", "You have selected 8") + +def test_slsl009_vertical_slider_with_input(dash_dcc): + app = dash.Dash(__name__) + app.layout = html.Div( + [ + dcc.Slider( + id="slider", + min=0, + max=20, + step=1, + value=5, + vertical=True, + syncedInput=True, + syncedInputID="syncedInput", + tooltip={"always_visible": True}, + ), + html.Div(id="out"), + ] + ) + + @app.callback(Output("out", "children"), [Input("slider", "value")]) + def update_output(value): + return "You have selected {}".format(value) + + dash_dcc.start_server(app) + dash_dcc.wait_for_text_to_equal("#out", "You have selected 5") + + input_ = dash_dcc.find_element("#syncedInput") + + input_.clear() + input_.send_keys("8") + + dash_dcc.wait_for_text_to_equal("#out", "You have selected 8") + +def test_slsl010_horizontal_slider_with_input_class_id(dash_dcc): + app = dash.Dash(__name__) + app.layout = html.Div( + [ + dcc.Slider( + id="slider", + min=0, + max=20, + step=1, + value=5, + vertical=True, + syncedInput=True, + syncedInputClassName="slider-input", + syncedInputID="arbitraryID", + tooltip={"always_visible": True}, + ), + html.Div(id="out"), + ] + ) + + @app.callback(Output("out", "children"), [Input("slider", "value")]) + def update_output(value): + return "You have selected {}".format(value) + + dash_dcc.start_server(app) + dash_dcc.wait_for_text_to_equal("#out", "You have selected 5") + + input_ = dash_dcc.find_element("#arbitraryID") + + assert input_.get_attribute("class") == "slider-input" diff --git a/tests/test_integration_1.py b/tests/test_integration_1.py index b202f9d0f..2a934f341 100644 --- a/tests/test_integration_1.py +++ b/tests/test_integration_1.py @@ -156,6 +156,67 @@ 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): + 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, + syncedInput=True, + ), + ], + style={"height": "500px"}, + ) + self.startServer(app) + + self.wait_for_element_by_css_selector("#horizontal-slider-with-input") + self.snapshot("horizontal slider with input") + + input_ = self.driver.find_element_by_id('syncedInput') + + input_.clear() + input_.send_keys("8") + + 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, + syncedInput=True, + ), + ], + style={"height": "500px"}, + ) + self.startServer(app) + + self.wait_for_element_by_css_selector("#vertical-slider-with-input") + self.snapshot("vertical slider with input") + + time.sleep(120) + + input_ = self.driver.find_element_by_id('syncedInput') + + input_.clear() + input_.send_keys("8") + + for entry in self.get_log(): + raise Exception("browser error logged during test", entry) + def test_loading_range_slider(self): lock = Lock() From 3e629129e7e4f46e4ec55b8a797c0c0ef55ee922 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 6 Apr 2021 17:39:47 -0400 Subject: [PATCH 14/22] add tests --- tests/integration/sliders/test_sliders.py | 7 +++++-- tests/test_integration_1.py | 18 ++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/tests/integration/sliders/test_sliders.py b/tests/integration/sliders/test_sliders.py index a4ef3ee18..f69ce5889 100644 --- a/tests/integration/sliders/test_sliders.py +++ b/tests/integration/sliders/test_sliders.py @@ -242,6 +242,7 @@ def update_output(value): dash_dcc.release() dash_dcc.wait_for_text_to_equal("#out-value", "You have selected 10-15") + def test_slsl008_horizontal_slider_with_input(dash_dcc): app = dash.Dash(__name__) app.layout = html.Div( @@ -274,6 +275,7 @@ def update_output(value): dash_dcc.wait_for_text_to_equal("#out", "You have selected 8") + def test_slsl009_vertical_slider_with_input(dash_dcc): app = dash.Dash(__name__) app.layout = html.Div( @@ -286,7 +288,7 @@ def test_slsl009_vertical_slider_with_input(dash_dcc): value=5, vertical=True, syncedInput=True, - syncedInputID="syncedInput", + syncedInputID="arbitraryID", tooltip={"always_visible": True}, ), html.Div(id="out"), @@ -300,13 +302,14 @@ def update_output(value): dash_dcc.start_server(app) dash_dcc.wait_for_text_to_equal("#out", "You have selected 5") - input_ = dash_dcc.find_element("#syncedInput") + input_ = dash_dcc.find_element("#arbitraryID") input_.clear() input_.send_keys("8") dash_dcc.wait_for_text_to_equal("#out", "You have selected 8") + def test_slsl010_horizontal_slider_with_input_class_id(dash_dcc): app = dash.Dash(__name__) app.layout = html.Div( diff --git a/tests/test_integration_1.py b/tests/test_integration_1.py index 2a934f341..afd7ca982 100644 --- a/tests/test_integration_1.py +++ b/tests/test_integration_1.py @@ -167,6 +167,7 @@ def test_horizontal_slider_with_input(self): min=0, max=9, value=5, + syncedInputClassName="arbitraryClassName", syncedInput=True, ), ], @@ -177,10 +178,9 @@ def test_horizontal_slider_with_input(self): self.wait_for_element_by_css_selector("#horizontal-slider-with-input") self.snapshot("horizontal slider with input") - input_ = self.driver.find_element_by_id('syncedInput') - - input_.clear() - input_.send_keys("8") + input_by_class_name = self.driver.find_element_by_class_name( + "arbitraryClassName" + ) for entry in self.get_log(): raise Exception("browser error logged during test", entry) @@ -197,6 +197,7 @@ def test_vertical_slider_with_input(self): max=9, value=5, vertical=True, + syncedInputClassName="arbitraryClassName", syncedInput=True, ), ], @@ -207,12 +208,9 @@ def test_vertical_slider_with_input(self): self.wait_for_element_by_css_selector("#vertical-slider-with-input") self.snapshot("vertical slider with input") - time.sleep(120) - - input_ = self.driver.find_element_by_id('syncedInput') - - input_.clear() - input_.send_keys("8") + input_by_class_name = self.driver.find_element_by_class_name( + "arbitraryClassName" + ) for entry in self.get_log(): raise Exception("browser error logged during test", entry) From 3820ce4b24560c865e26e1c63f342783c371d93e Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 6 Apr 2021 17:59:29 -0400 Subject: [PATCH 15/22] lint tests --- src/components/Slider.react.js | 6 +++--- tests/test_integration_1.py | 12 ++++-------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/components/Slider.react.js b/src/components/Slider.react.js index defceb4c6..96b5b98b6 100644 --- a/src/components/Slider.react.js +++ b/src/components/Slider.react.js @@ -115,7 +115,7 @@ Slider.propTypes = { }), /** - * Value by which increments or decrements are made + * Value by which increments or decrements are made. */ step: PropTypes.number, @@ -130,7 +130,7 @@ Slider.propTypes = { syncedInputClassName: PropTypes.string, /** - * The CSS to be applied to the class of the input (div) + * The CSS to be applied to the class of the input (div). */ syncedInputStyle: PropTypes.object, @@ -140,7 +140,7 @@ Slider.propTypes = { syncedInputID: PropTypes.string, /** - * The CSS to be applied to the class of the slider (div) + * The CSS to be applied to the class of the slider (div). */ style: PropTypes.object, diff --git a/tests/test_integration_1.py b/tests/test_integration_1.py index afd7ca982..aca050678 100644 --- a/tests/test_integration_1.py +++ b/tests/test_integration_1.py @@ -176,11 +176,9 @@ def test_horizontal_slider_with_input(self): self.startServer(app) self.wait_for_element_by_css_selector("#horizontal-slider-with-input") - self.snapshot("horizontal slider with input") + self.wait_for_element_by_css_selector(".arbitraryClassName") - input_by_class_name = self.driver.find_element_by_class_name( - "arbitraryClassName" - ) + self.snapshot("horizontal slider with input") for entry in self.get_log(): raise Exception("browser error logged during test", entry) @@ -206,11 +204,9 @@ def test_vertical_slider_with_input(self): self.startServer(app) self.wait_for_element_by_css_selector("#vertical-slider-with-input") - self.snapshot("vertical slider with input") + self.wait_for_element_by_css_selector(".arbitraryClassName") - input_by_class_name = self.driver.find_element_by_class_name( - "arbitraryClassName" - ) + self.snapshot("vertical slider with input") for entry in self.get_log(): raise Exception("browser error logged during test", entry) From 884cccb7647d3746969a190494dd88ecab2944c8 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 6 Apr 2021 18:04:41 -0400 Subject: [PATCH 16/22] run prettier --- src/components/Slider.react.js | 4 ++-- src/fragments/Slider.react.js | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/Slider.react.js b/src/components/Slider.react.js index 96b5b98b6..e35da2fc7 100644 --- a/src/components/Slider.react.js +++ b/src/components/Slider.react.js @@ -137,7 +137,7 @@ Slider.propTypes = { /** * The id to be applied to the input (div). Default is "syncedInput". */ - syncedInputID: PropTypes.string, + syncedInputID: PropTypes.string, /** * The CSS to be applied to the class of the slider (div). @@ -230,7 +230,7 @@ Slider.defaultProps = { persistence_type: 'local', verticalHeight: 400, syncedInputDebounceTime: 450, - syncedInputID: "syncedInput" + syncedInputID: 'syncedInput', }; export const propTypes = Slider.propTypes; diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index dc36285a7..ecae1ed3a 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -24,9 +24,12 @@ export default class Slider extends Component { } syncInput(event) { - if(event){ + if (event) { this.setState({value: Number(event.target.value)}); - this.props.setProps({value: Number(event.target.value), drag_value: Number(event.target.value)}); + this.props.setProps({ + value: Number(event.target.value), + drag_value: Number(event.target.value), + }); } } @@ -123,11 +126,11 @@ export default class Slider extends Component { ); }} onBlur={event => { - this.syncInput(event) + this.syncInput(event); }} onKeyPress={event => { if (event.key === 'Enter') { - this.syncInput() + this.syncInput(); } }} type="number" From 403ee9e4ea8c316254b9704a7039a66af9d96ffc Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 6 Apr 2021 19:51:09 -0400 Subject: [PATCH 17/22] small fixups --- src/fragments/Slider.react.js | 14 ++++++-------- tests/integration/sliders/test_sliders.py | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index ecae1ed3a..110563896 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -100,8 +100,8 @@ export default class Slider extends Component { const defaultInputStyle = { width: '60px', - marginRight: vertical ? '' : '25px', - marginBottom: vertical ? '25px' : '', + marginRight: vertical && syncedInput ? '' : '25px', + marginBottom: vertical && syncedInput ? '25px' : '', }; return ( @@ -121,7 +121,9 @@ export default class Slider extends Component { clearTimeout(this.timeout); } this.timeout = setTimeout( - this.syncInput(event), + function() { + this.syncInput(event); + }.bind(this), syncedInputDebounceTime ); }} @@ -163,11 +165,7 @@ export default class Slider extends Component { ...tipProps, getTooltipContainer: node => node, }} - style={{ - position: 'relative', - float: 'left', - width: syncedInput ? '85%' : '100%', - }} + style={{position: 'relative'}} value={value} marks={truncatedMarks} {...omit( diff --git a/tests/integration/sliders/test_sliders.py b/tests/integration/sliders/test_sliders.py index f69ce5889..819eadff1 100644 --- a/tests/integration/sliders/test_sliders.py +++ b/tests/integration/sliders/test_sliders.py @@ -310,7 +310,7 @@ def update_output(value): dash_dcc.wait_for_text_to_equal("#out", "You have selected 8") -def test_slsl010_horizontal_slider_with_input_class_id(dash_dcc): +def test_slsl010_horizontal_slider_with_input(dash_dcc): app = dash.Dash(__name__) app.layout = html.Div( [ From 761103c493f58a787e37158680f40b2c9def5146 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Thu, 8 Apr 2021 05:30:25 -0400 Subject: [PATCH 18/22] make sure that pressing enter when input is focused changes state of slider --- src/fragments/Slider.react.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index 110563896..44a4d8612 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -132,7 +132,7 @@ export default class Slider extends Component { }} onKeyPress={event => { if (event.key === 'Enter') { - this.syncInput(); + this.syncInput(event); } }} type="number" From 1709c213ad07d7f8b07918cfe2c0762b7fe448b3 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Thu, 8 Apr 2021 05:35:54 -0400 Subject: [PATCH 19/22] clear debounce timeout after every state sync --- src/fragments/Slider.react.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index 44a4d8612..4bedfec67 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -24,6 +24,9 @@ export default class Slider extends Component { } syncInput(event) { + if (this.timeout) { + clearTimeout(this.timeout); + } if (event) { this.setState({value: Number(event.target.value)}); this.props.setProps({ @@ -117,9 +120,6 @@ export default class Slider extends Component { { event.persist(); - if (this.timeout) { - clearTimeout(this.timeout); - } this.timeout = setTimeout( function() { this.syncInput(event); From 4b4528479441918589a80e9cf4e57bc9679d6161 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 13 Apr 2021 17:28:11 -0400 Subject: [PATCH 20/22] refactor synced input event handling --- src/fragments/Slider.react.js | 52 ++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index 4bedfec67..f154100d5 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -2,7 +2,6 @@ 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'; @@ -17,23 +16,32 @@ export default class Slider extends Component { this.DashSlider = props.tooltip ? createSliderWithTooltip(ReactSlider) : ReactSlider; - this.SyncedInput = Input; this._computeStyle = computeSliderStyle(); this.state = {value: props.value}; - this.syncInput = this.syncInput.bind(this); + this.syncInputWithSlider = this.syncInputWithSlider.bind(this); + this.input = React.createRef(); } - syncInput(event) { + syncInputWithSlider() { + if (this.input.current.value > this.props.max) { + this.input.current.value = this.props.max; + } + + if (this.input.current.value < this.props.min) { + this.input.current.value = this.props.min; + } + if (this.timeout) { clearTimeout(this.timeout); } - if (event) { - this.setState({value: Number(event.target.value)}); - this.props.setProps({ - value: Number(event.target.value), - drag_value: Number(event.target.value), - }); - } + + const valueAsNumber = Number(this.input.current.value); + + this.setState({value: valueAsNumber}); + this.props.setProps({ + value: valueAsNumber, + drag_value: valueAsNumber, + }); } UNSAFE_componentWillReceiveProps(newProps) { @@ -117,30 +125,30 @@ export default class Slider extends Component { style={{...computedStyle, ...style}} > {syncedInput ? ( - { - event.persist(); + { this.timeout = setTimeout( function() { - this.syncInput(event); + this.syncInputWithSlider(); }.bind(this), syncedInputDebounceTime ); }} - onBlur={event => { - this.syncInput(event); + onBlur={() => { + this.syncInputWithSlider(); }} onKeyPress={event => { if (event.key === 'Enter') { - this.syncInput(event); + this.syncInputWithSlider(); } }} type="number" - value={value} + defaultValue={value} step={step} className={syncedInputClassName} id={syncedInputID} style={{...defaultInputStyle, ...syncedInputStyle}} + ref={this.input} /> ) : null} { if (updatemode === 'mouseup') { setProps({value}); } + if (syncedInput) { + this.input.current.value = value; + } }} /* if/when rc-slider or rc-tooltip are updated to latest versions, From 0111a749155289ab4e4deb84b5bfb93844b17731 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Tue, 27 Apr 2021 14:25:12 -0400 Subject: [PATCH 21/22] camelCase to snake_case new props --- src/components/Slider.react.js | 14 +++++++------- src/fragments/Slider.react.js | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/components/Slider.react.js b/src/components/Slider.react.js index e35da2fc7..30372213b 100644 --- a/src/components/Slider.react.js +++ b/src/components/Slider.react.js @@ -122,22 +122,22 @@ Slider.propTypes = { /** * If true, display an Input component whose value is synced with the Slider's value. */ - syncedInput: PropTypes.bool, + synced_input: PropTypes.bool, /** * The classname to be given to the synced Input component. */ - syncedInputClassName: PropTypes.string, + synced_input_class_name: PropTypes.string, /** * The CSS to be applied to the class of the input (div). */ - syncedInputStyle: PropTypes.object, + synced_input_style: PropTypes.object, /** * The id to be applied to the input (div). Default is "syncedInput". */ - syncedInputID: PropTypes.string, + synced_input_id: PropTypes.string, /** * The CSS to be applied to the class of the slider (div). @@ -147,7 +147,7 @@ Slider.propTypes = { /** * The amount of time the synced Input should wait before passing along state changes without a change of focus or the user pressing Enter. In milliseconds. */ - syncedInputDebounceTime: PropTypes.number, + synced_input_debounce_time: PropTypes.number, /** * If true, the slider will be vertical @@ -229,8 +229,8 @@ Slider.defaultProps = { persisted_props: ['value'], persistence_type: 'local', verticalHeight: 400, - syncedInputDebounceTime: 450, - syncedInputID: 'syncedInput', + synced_input_debounce_time: 450, + synced_input_id: 'syncedInput', }; export const propTypes = Slider.propTypes; diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index f154100d5..4ab5abe1b 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -71,11 +71,11 @@ export default class Slider extends Component { setProps, tooltip, updatemode, - syncedInput, - syncedInputDebounceTime, - syncedInputClassName, - syncedInputStyle, - syncedInputID, + synced_input, + synced_input_debounce_time, + synced_input_class_name, + synced_input_style, + synced_input_id, style, step, vertical, @@ -111,8 +111,8 @@ export default class Slider extends Component { const defaultInputStyle = { width: '60px', - marginRight: vertical && syncedInput ? '' : '25px', - marginBottom: vertical && syncedInput ? '25px' : '', + marginRight: vertical && synced_input ? '' : '25px', + marginBottom: vertical && synced_input ? '25px' : '', }; return ( @@ -124,14 +124,14 @@ export default class Slider extends Component { className={className} style={{...computedStyle, ...style}} > - {syncedInput ? ( + {synced_input ? ( { this.timeout = setTimeout( function() { this.syncInputWithSlider(); }.bind(this), - syncedInputDebounceTime + synced_input_debounce_time ); }} onBlur={() => { @@ -145,9 +145,9 @@ export default class Slider extends Component { type="number" defaultValue={value} step={step} - className={syncedInputClassName} - id={syncedInputID} - style={{...defaultInputStyle, ...syncedInputStyle}} + className={synced_input_class_name} + id={synced_input_id} + style={{...defaultInputStyle, ...synced_input_style}} ref={this.input} /> ) : null} @@ -159,7 +159,7 @@ export default class Slider extends Component { this.setState({value: value}); setProps({drag_value: value}); } - if (syncedInput) { + if (synced_input) { this.input.current.value = value; } }} @@ -167,7 +167,7 @@ export default class Slider extends Component { if (updatemode === 'mouseup') { setProps({value}); } - if (syncedInput) { + if (synced_input) { this.input.current.value = value; } }} From aebce8dc6080efcf53ae0486b723fd16b3daede0 Mon Sep 17 00:00:00 2001 From: Joseph Damiba Date: Fri, 21 May 2021 11:10:56 -0400 Subject: [PATCH 22/22] add some ui ;ogic --- src/fragments/Slider.react.js | 42 ++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/fragments/Slider.react.js b/src/fragments/Slider.react.js index 4ab5abe1b..221f662c4 100644 --- a/src/fragments/Slider.react.js +++ b/src/fragments/Slider.react.js @@ -7,6 +7,10 @@ import 'rc-slider/assets/index.css'; import {propTypes, defaultProps} from '../components/Slider.react'; +function round(number, increment, offset) { + return Math.round((number - offset) / increment ) * increment + offset; +} + /** * A slider component with a single handle. */ @@ -22,25 +26,37 @@ export default class Slider extends Component { this.input = React.createRef(); } - syncInputWithSlider() { - if (this.input.current.value > this.props.max) { + syncInputWithSlider(trigger) { + + if (trigger == "onChange"){ + if (this.timeout) { + clearTimeout(this.timeout); + } + return + } + + if (this.input.current.value === ""){ + this.input.current.value = this.props.value + } + + if (Number(this.input.current.value) > this.props.max) { this.input.current.value = this.props.max; } - if (this.input.current.value < this.props.min) { + if (Number(this.input.current.value) < this.props.min) { this.input.current.value = this.props.min; } - if (this.timeout) { - clearTimeout(this.timeout); + if (this.props.step){ + if ((this.input.current.value - this.props.step) % 1 !== 0){ + this.input.current.value = round(this.input.current.value, this.props.step, 0) + } } - const valueAsNumber = Number(this.input.current.value); - - this.setState({value: valueAsNumber}); + this.setState({value: Number(this.input.current.value)}); this.props.setProps({ - value: valueAsNumber, - drag_value: valueAsNumber, + value: Number(this.input.current.value), + drag_value: Number(this.input.current.value), }); } @@ -129,17 +145,17 @@ export default class Slider extends Component { onChange={() => { this.timeout = setTimeout( function() { - this.syncInputWithSlider(); + this.syncInputWithSlider("onChange"); }.bind(this), synced_input_debounce_time ); }} onBlur={() => { - this.syncInputWithSlider(); + this.syncInputWithSlider("onBlur"); }} onKeyPress={event => { if (event.key === 'Enter') { - this.syncInputWithSlider(); + this.syncInputWithSlider("onKeyPress"); } }} type="number"