From 9526b17ee148900fda0258dc3fa7b04cf3573ae6 Mon Sep 17 00:00:00 2001 From: Christopher Huyler Date: Fri, 29 Apr 2022 10:24:36 -0400 Subject: [PATCH] Fix: Datepicker: Unable to enter date manually --- .../__tests__/date-picker.browser-test.jsx | 124 ++++++++++++++++++ components/date-picker/date-picker.jsx | 69 ++++++---- 2 files changed, 168 insertions(+), 25 deletions(-) diff --git a/components/date-picker/__tests__/date-picker.browser-test.jsx b/components/date-picker/__tests__/date-picker.browser-test.jsx index a44c29c473..00ddbaaadb 100644 --- a/components/date-picker/__tests__/date-picker.browser-test.jsx +++ b/components/date-picker/__tests__/date-picker.browser-test.jsx @@ -431,6 +431,130 @@ describe('SLDSDatepicker', function describeFunction() { input.simulate('change', { target: { value: '1/1/2020' } }); expect(wrapper.find('.slds-datepicker').length).to.equal(0); }); + + it('typing partial date resets after pressing enter', function () { + const handleChangeSpy = sinon.spy(); + wrapper = mount( + + ); + + // Calendar is closed + expect(wrapper.find('.slds-datepicker').length).to.equal(0); + + // Click on input to open the calendar + const trigger = wrapper.find(triggerClassSelector); + trigger.simulate('click', {}); + expect(wrapper.find('.slds-datepicker').length).to.equal(1); + + // Changing input value closes the calendar + const input = wrapper.find('input#sample-datepicker'); + + input.simulate('change', { target: { value: '' } }); + input.simulate('click', {}); + input.simulate('keyDown', { key: '9', keyCode: 49, which: 49 }); + input.simulate('keyDown', { key: '/', keyCode: 191, which: 191 }); + input.simulate('change', { target: { value: '9/' } }); + expect(input.instance().value).to.equal('9/'); + input.simulate('keyDown', { + key: 'Enter', + keyCode: KEYS.ENTER, + which: KEYS.ENTER, + }); + expect(input.instance().value).to.equal('1/6/2007'); + expect(handleChangeSpy.calledOnce).to.equal(true); + }); + + it('typing partial date resets after pressing escape', function () { + const handleChangeSpy = sinon.spy(); + wrapper = mount( + + ); + + // Calendar is closed + expect(wrapper.find('.slds-datepicker').length).to.equal(0); + + // Click on input to open the calendar + const trigger = wrapper.find(triggerClassSelector); + trigger.simulate('click', {}); + expect(wrapper.find('.slds-datepicker').length).to.equal(1); + + // Changing input value closes the calendar + const input = wrapper.find('input#sample-datepicker'); + + input.simulate('change', { target: { value: '' } }); + input.simulate('click', {}); + input.simulate('keyDown', { key: '9', keyCode: 49, which: 49 }); + input.simulate('keyDown', { key: '/', keyCode: 191, which: 191 }); + input.simulate('change', { target: { value: '9/' } }); + input.simulate('change', { target: { value: '9/' } }); + expect(input.instance().value).to.equal('9/'); + input.simulate('keyDown', { + key: 'Escape', + keyCode: KEYS.ESCAPE, + which: KEYS.ESCAPE, + }); + expect(input.instance().value).to.equal('1/6/2007'); + expect(handleChangeSpy.calledOnce).to.equal(false); + }); + + it('typing complete date resets after pressing escape', function () { + const handleChangeSpy = sinon.spy(); + wrapper = mount( + + ); + + // Calendar is closed + expect(wrapper.find('.slds-datepicker').length).to.equal(0); + + // Click on input to open the calendar + const trigger = wrapper.find(triggerClassSelector); + trigger.simulate('click', {}); + expect(wrapper.find('.slds-datepicker').length).to.equal(1); + + // Changing input value closes the calendar + const input = wrapper.find('input#sample-datepicker'); + + input.simulate('change', { target: { value: '' } }); + input.simulate('click', {}); + input.simulate('change', { target: { value: '12/31/2007' } }); + expect(input.instance().value).to.equal('12/31/2007'); + input.simulate('keyDown', { + key: 'Escape', + keyCode: KEYS.ESCAPE, + which: KEYS.ESCAPE, + }); + expect(input.instance().value).to.equal('1/6/2007'); + expect(handleChangeSpy.calledOnce).to.equal(false); + }); + + it('typing complete date saves after pressing enter', function () { + const handleChangeSpy = sinon.spy(); + wrapper = mount( + + ); + + // Calendar is closed + expect(wrapper.find('.slds-datepicker').length).to.equal(0); + + // Click on input to open the calendar + const trigger = wrapper.find(triggerClassSelector); + trigger.simulate('click', {}); + expect(wrapper.find('.slds-datepicker').length).to.equal(1); + + // Changing input value closes the calendar + const input = wrapper.find('input#sample-datepicker'); + + input.simulate('change', { target: { value: '' } }); + input.simulate('click', {}); + input.simulate('change', { target: { value: '12/31/2007' } }); + expect(input.instance().value).to.equal('12/31/2007'); + input.simulate('keyDown', { + key: 'Enter', + keyCode: KEYS.ENTER, + which: KEYS.ENTER, + }); + expect(handleChangeSpy.calledOnce).to.equal(true); + }); }); }); diff --git a/components/date-picker/date-picker.jsx b/components/date-picker/date-picker.jsx index ee8ee069e0..de9a68c341 100644 --- a/components/date-picker/date-picker.jsx +++ b/components/date-picker/date-picker.jsx @@ -260,17 +260,9 @@ const defaultProps = { class Datepicker extends React.Component { constructor(props) { super(props); - // Please remove `strValue` on the next breaking change. - const formattedValue = props.formattedValue || props.strValue; // eslint-disable-line react/prop-types - const dateString = props.formatter(props.value); - const initDate = props.value ? dateString : formattedValue; - this.state = { isOpen: false, isOpenFromIcon: false, - value: props.value, - formattedValue: initDate || '', - inputValue: initDate || '', }; this.generatedId = shortid.generate(); @@ -279,6 +271,22 @@ class Datepicker extends React.Component { checkProps(DATE_PICKER, props, componentDoc); } + static getDerivedStateFromProps(props, state) { + if (props.value !== state.value) { + const formattedValue = props.formattedValue || props.strValue; // eslint-disable-line react/prop-types + const dateString = props.formatter(props.value); + const initDate = props.value ? dateString : formattedValue; + return { + isOpen: false, + isOpenFromIcon: false, + value: props.value, + formattedValue: initDate || '', + inputValue: initDate || '', + }; + } + return state; + } + getDatePicker = ({ labels, assistiveText }) => { let date; // Use props if present. Otherwise, use state. @@ -416,9 +424,7 @@ class Datepicker extends React.Component { this.openDialog(); }, onKeyDown: this.handleKeyDown, - value: this.props.value - ? this.props.formatter(this.props.value) - : this.state.inputValue, + value: this.state.inputValue, }; // eslint-disable react/prop-types @@ -512,16 +518,6 @@ class Datepicker extends React.Component { formattedValue: event.target.value, inputValue: event.target.value, }); - - const date = this.props.parser(event.target.value); - - if (this.props.onChange) { - this.props.onChange(event, { - date, - formattedDate: event.target.value, - timezoneOffset: date.getTimezoneOffset(), - }); - } }; handleKeyDown = (event) => { @@ -535,9 +531,31 @@ class Datepicker extends React.Component { this.setState({ isOpen: true }); } - if (event.keyCode === KEYS.ESCAPE || event.keyCode === KEYS.ENTER) { + if (event.keyCode === KEYS.ESCAPE) { EventUtil.trapEvent(event); - this.setState({ isOpen: false }); + this.setState(Datepicker.getDerivedStateFromProps(this.props, {})); + } + + if (event.keyCode === KEYS.ENTER || event.keyCode === KEYS.TAB) { + const date = this.props.parser(event.target.value); + const formattedDate = this.props.formatter(date); + if (formattedDate !== 'Invalid date') { + this.setState({ + value: date, + formattedValue: formattedDate, + inputValue: formattedDate, + isOpen: false, + }); + if (this.props.onChange) { + this.props.onChange(event, { + date, + formattedDate, + timezoneOffset: date.getTimezoneOffset(), + }); + } + } else { + this.setState(Datepicker.getDerivedStateFromProps(this.props, {})); + } } // Please remove `onKeyDown` on the next breaking change. @@ -563,9 +581,10 @@ class Datepicker extends React.Component { this.props.onRequestClose(); } - if (this.getIsOpen()) { - this.setState({ isOpen: false, isOpenFromIcon: false }); + const wasOpen = this.getIsOpen(); + this.setState(Datepicker.getDerivedStateFromProps(this.props, {})); + if (wasOpen) { if (this.inputRef) { this.inputRef.focus(); }