From 683e412d540f7e00225468b5200f7b7515359649 Mon Sep 17 00:00:00 2001 From: Rodrigo Arede Date: Wed, 24 Jul 2024 22:48:55 +0100 Subject: [PATCH] =?UTF-8?q?Implement=20currencyFormat=20for=20Plotly=20Das?= =?UTF-8?q?h=20With=20this=20commit=20it=20is=20now=20allowed=20currency?= =?UTF-8?q?=20formatting=20by=20setting=20the=20currencyFormat=20property?= =?UTF-8?q?=20to=20True.=20When=20currencyFormat=20is=20enabled,=20the=20c?= =?UTF-8?q?urrencySymbol=20property=20=20allows=20you=20to=20specify=20the?= =?UTF-8?q?=20currency=20symbol=20to=20be=20used.=20For=20example,=20if=20?= =?UTF-8?q?the=20input=20value=20is=2012345,=20and=20currencyFormat=20=3D?= =?UTF-8?q?=20true,=20currencySymbol=20=3D=20'=E2=82=AC'=20it=20will=20be?= =?UTF-8?q?=20displayed=20as=20=E2=82=AC12345.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Afonso Faleiro --- .../src/components/Input.react.js | 97 ++++++++++++++++--- .../integration/input/test_number_input.py | 23 +++++ 2 files changed, 109 insertions(+), 11 deletions(-) diff --git a/components/dash-core-components/src/components/Input.react.js b/components/dash-core-components/src/components/Input.react.js index d4656fddae..307f20a649 100644 --- a/components/dash-core-components/src/components/Input.react.js +++ b/components/dash-core-components/src/components/Input.react.js @@ -31,6 +31,8 @@ const inputProps = [ 'size', 'style', 'id', + 'currencyFormat', + 'currencySymbol', ]; /** @@ -46,7 +48,10 @@ export default class Input extends PureComponent { this.state = { pendingEvent: undefined, - value: '', + value: '' | props.value, + displayValue: props.currencyFormat + ? this.formatCurrency(props.value) + : props.value, }; this.input = React.createRef(); @@ -58,6 +63,7 @@ export default class Input extends PureComponent { this.debounceEvent = this.debounceEvent.bind(this); this.setInputValue = this.setInputValue.bind(this); this.setPropValue = this.setPropValue.bind(this); + this.handleInputChange = this.handleInputChange.bind(this); } UNSAFE_componentWillReceiveProps(nextProps) { @@ -90,25 +96,58 @@ export default class Input extends PureComponent { this.setState({value: this.props.value}); } } + formatCurrency(value) { + if (isNaN(value) || value === '') { + return ''; + } + const {currencySymbol} = this.props; + + return `${currencySymbol}${Number(value).toLocaleString()}`; + } + parseDisplayValue(value) { + if (!value) { + return ''; + } + // Remove currency symbol and non-numeric characters + return Number(value.replace(/[^0-9.-]+/g, '')); + } + handleInputChange(event) { + const {value} = event.target; + const valueAsNumber = this.parseDisplayValue(value); + this.setState({ + value: valueAsNumber, + displayValue: this.props.currencyFormat + ? this.formatCurrency(valueAsNumber) + : valueAsNumber, + }); + } render() { - const valprops = - this.props.type === 'number' ? {} : {value: this.state.value}; - const {loading_state} = this.props; + const {loading_state, currencyFormat} = this.props; + let valprops; + if (this.props.type === 'number' && !currencyFormat) { + valprops = { value: this.state.value }; // Always show the number + } else { + valprops = { value: this.state.displayValue }; // Use formatted display value for currency + } let {className} = this.props; + + const inputType = currencyFormat ? 'text' : 'number'; + className = 'dash-input' + (className ? ` ${className}` : ''); return ( ); } @@ -119,7 +158,14 @@ export default class Input extends PureComponent { value = convert(value); if (!isEquivalent(base, value)) { - this.input.current.value = isNumeric(value) ? value : __value; + if (isNumeric(value)) { + const formattedValue = this.props.currencyFormat + ? this.formatCurrency(value) + : value; + this.setState({displayValue: formattedValue}, () => {}); + } else { + this.setState({displayValue: __value}); + } } } @@ -131,18 +177,22 @@ export default class Input extends PureComponent { this.props.setProps({value}); } } - onEvent() { const {value} = this.input.current; - const valueAsNumber = convert(value); + const valueAsNumber = this.parseDisplayValue(value); + if (this.props.type === 'number') { this.setPropValue( this.props.value, - isNil(valueAsNumber) ? value : valueAsNumber + isNaN(valueAsNumber) ? value : valueAsNumber ); } else { - this.props.setProps({value}); + const formattedValue = this.props.currencyFormat + ? this.formatCurrency(valueAsNumber) + : value; + this.props.setProps({value: formattedValue}); } + this.setState({pendingEvent: undefined}); } @@ -207,6 +257,8 @@ Input.defaultProps = { step: 'any', persisted_props: ['value'], persistence_type: 'local', + currencyFormat: false, + currencySymbol: '$', }; Input.propTypes = { @@ -239,6 +291,29 @@ Input.propTypes = { */ debounce: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]), + /** + * If true and type is 'number', formats the input value as currency. + */ + currencyFormat: (props, propName, componentName) => { + if (props.currencyFormat && props.type !== 'number') { + return new Error( + `Invalid prop \`${propName}\` supplied to ` + + `\`${componentName}\`. \`${propName}\` can only be used when \`type\` is \`number\`.` + ); + } + return null; // Ensure to return null if there’s no error + }, + + currencySymbol: (props, propName, componentName) => { + if (props.currencySymbol && props.type !== 'number') { + return new Error( + `Invalid prop \`${propName}\` supplied to ` + + `\`${componentName}\`. \`${propName}\` can only be used when \`type\` is \`number\`.` + ); + } + return null; // Ensure to return null if there’s no error + }, + /** * A hint to the user of what can be entered in the control . The placeholder text must not contain carriage returns or line-feeds. Note: Do not use the placeholder attribute instead of a