diff --git a/package.json b/package.json index 7db20656..b210cd44 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "dependencies": { "@types/react-calendar": "^3.0.0", "@wojtekmaj/date-utils": "^1.0.3", + "@wojtekmaj/predict-input-value": "^1.0.0", "get-user-locale": "^1.2.0", "make-event-props": "^1.1.0", "merge-class-names": "^1.1.1", diff --git a/src/DateInput.jsx b/src/DateInput.jsx index 3c1ed4c7..527b66c4 100644 --- a/src/DateInput.jsx +++ b/src/DateInput.jsx @@ -108,6 +108,28 @@ function findInput(element, property) { return nextElement; } +function isInputValid(input) { + if (!input.validity.valid) { + return false; + } + + const { value } = input; + + if (!value) { + return false; + } + + const rawValue = Number(value); + const min = Number(input.getAttribute('min')); + const max = Number(input.getAttribute('max')); + + if (rawValue < min || rawValue > max) { + return false; + } + + return true; +} + function focus(element) { if (element) { element.focus(); @@ -192,9 +214,9 @@ export default class DateInput extends PureComponent { ) ) { if (nextValue) { - nextState.year = getYear(nextValue); - nextState.month = getMonthHuman(nextValue); - nextState.day = getDate(nextValue); + nextState.year = getYear(nextValue).toString(); + nextState.month = getMonthHuman(nextValue).toString(); + nextState.day = getDate(nextValue).toString(); } else { nextState.year = null; nextState.month = null; @@ -352,7 +374,7 @@ export default class DateInput extends PureComponent { return; } - const { value } = input; + const { maxLength, value } = input; const max = input.getAttribute('max'); /** @@ -361,7 +383,7 @@ export default class DateInput extends PureComponent { * However, given 2, smallers possible number would be 20, and thus keeping the focus in * this field doesn't make sense. */ - if ((value * 10 > max) || (value.length >= max.length)) { + if ((value * 10 > max) || (value.length >= maxLength)) { const property = 'nextElementSibling'; const nextInput = findInput(input, property); focus(nextInput); @@ -375,7 +397,7 @@ export default class DateInput extends PureComponent { const { name, value } = event.target; this.setState( - { [name]: value ? parseInt(value, 10) : null }, + { [name]: value }, this.onChangeExternal, ); } @@ -397,9 +419,9 @@ export default class DateInput extends PureComponent { } const [yearString, monthString, dayString] = value.split('-'); - const year = parseInt(yearString, 10); - const monthIndex = parseInt(monthString, 10) - 1 || 0; - const day = parseInt(dayString, 10) || 1; + const year = Number(yearString); + const monthIndex = Number(monthString) - 1 || 0; + const day = Number(dayString) || 1; const proposedValue = new Date(); proposedValue.setFullYear(year, monthIndex, day); @@ -431,12 +453,10 @@ export default class DateInput extends PureComponent { if (formElements.every((formElement) => !formElement.value)) { onChange(null, false); - } else if ( - formElements.every((formElement) => formElement.value && formElement.validity.valid) - ) { - const year = parseInt(values.year, 10); - const monthIndex = parseInt(values.month, 10) - 1 || 0; - const day = parseInt(values.day || 1, 10); + } else if (formElements.every(isInputValid)) { + const year = Number(values.year); + const monthIndex = Number(values.month) - 1 || 0; + const day = Number(values.day || 1); const proposedValue = new Date(); proposedValue.setFullYear(year, monthIndex, day); diff --git a/src/DateInput.spec.jsx b/src/DateInput.spec.jsx index e17baf89..7fd86be1 100644 --- a/src/DateInput.spec.jsx +++ b/src/DateInput.spec.jsx @@ -115,9 +115,9 @@ describe('DateInput', () => { const customInputs = component.find('input[data-input]'); expect(nativeInput.prop('value')).toBe('2017-09-30'); - expect(customInputs.at(0).prop('value')).toBe(9); - expect(customInputs.at(1).prop('value')).toBe(30); - expect(customInputs.at(2).prop('value')).toBe(2017); + expect(customInputs.at(0).prop('value')).toBe('9'); + expect(customInputs.at(1).prop('value')).toBe('30'); + expect(customInputs.at(2).prop('value')).toBe('2017'); }); it('shows a given date in all inputs correctly given array of Date objects (12-hour format)', () => { @@ -134,9 +134,9 @@ describe('DateInput', () => { const customInputs = component.find('input[data-input]'); expect(nativeInput.prop('value')).toBe('2017-09-30'); - expect(customInputs.at(0).prop('value')).toBe(9); - expect(customInputs.at(1).prop('value')).toBe(30); - expect(customInputs.at(2).prop('value')).toBe(2017); + expect(customInputs.at(0).prop('value')).toBe('9'); + expect(customInputs.at(1).prop('value')).toBe('30'); + expect(customInputs.at(2).prop('value')).toBe('2017'); }); it('shows a given date in all inputs correctly given ISO string (12-hour format)', () => { @@ -153,9 +153,9 @@ describe('DateInput', () => { const customInputs = component.find('input[data-input]'); expect(nativeInput.prop('value')).toBe('2017-09-30'); - expect(customInputs.at(0).prop('value')).toBe(9); - expect(customInputs.at(1).prop('value')).toBe(30); - expect(customInputs.at(2).prop('value')).toBe(2017); + expect(customInputs.at(0).prop('value')).toBe('9'); + expect(customInputs.at(1).prop('value')).toBe('30'); + expect(customInputs.at(2).prop('value')).toBe('2017'); }); itIfFullICU('shows a given date in all inputs correctly given Date (24-hour format)', () => { @@ -173,9 +173,9 @@ describe('DateInput', () => { const customInputs = component.find('input[data-input]'); expect(nativeInput.prop('value')).toBe('2017-09-30'); - expect(customInputs.at(0).prop('value')).toBe(2017); - expect(customInputs.at(1).prop('value')).toBe(9); - expect(customInputs.at(2).prop('value')).toBe(30); + expect(customInputs.at(0).prop('value')).toBe('2017'); + expect(customInputs.at(1).prop('value')).toBe('9'); + expect(customInputs.at(2).prop('value')).toBe('30'); }); itIfFullICU('shows a given date in all inputs correctly given array of Date objects (24-hour format)', () => { @@ -193,9 +193,9 @@ describe('DateInput', () => { const customInputs = component.find('input[data-input]'); expect(nativeInput.prop('value')).toBe('2017-09-30'); - expect(customInputs.at(0).prop('value')).toBe(2017); - expect(customInputs.at(1).prop('value')).toBe(9); - expect(customInputs.at(2).prop('value')).toBe(30); + expect(customInputs.at(0).prop('value')).toBe('2017'); + expect(customInputs.at(1).prop('value')).toBe('9'); + expect(customInputs.at(2).prop('value')).toBe('30'); }); itIfFullICU('shows a given date in all inputs correctly given ISO string (24-hour format)', () => { @@ -213,9 +213,9 @@ describe('DateInput', () => { const customInputs = component.find('input[data-input]'); expect(nativeInput.prop('value')).toBe('2017-09-30'); - expect(customInputs.at(0).prop('value')).toBe(2017); - expect(customInputs.at(1).prop('value')).toBe(9); - expect(customInputs.at(2).prop('value')).toBe(30); + expect(customInputs.at(0).prop('value')).toBe('2017'); + expect(customInputs.at(1).prop('value')).toBe('9'); + expect(customInputs.at(2).prop('value')).toBe('30'); }); it('shows empty value in all inputs correctly given null', () => { diff --git a/src/DateInput/DayInput.jsx b/src/DateInput/DayInput.jsx index 05f697ac..b3a9e2f1 100644 --- a/src/DateInput/DayInput.jsx +++ b/src/DateInput/DayInput.jsx @@ -28,7 +28,7 @@ export default function DayInput({ })(); function isSameMonth(date) { - return date && year === getYear(date) && month === getMonthHuman(date); + return date && Number(year) === getYear(date) && Number(month) === getMonthHuman(date); } const maxDay = safeMin(currentMonthMaxDays, isSameMonth(maxDate) && getDate(maxDate)); @@ -44,6 +44,8 @@ export default function DayInput({ ); } +const isValue = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); + DayInput.propTypes = { ariaLabel: PropTypes.string, className: PropTypes.string.isRequired, @@ -51,13 +53,13 @@ DayInput.propTypes = { itemRef: PropTypes.func, maxDate: isMaxDate, minDate: isMinDate, - month: PropTypes.number, + month: isValue, onChange: PropTypes.func, onKeyDown: PropTypes.func, onKeyUp: PropTypes.func, placeholder: PropTypes.string, required: PropTypes.bool, showLeadingZeros: PropTypes.bool, - value: PropTypes.number, - year: PropTypes.number, + value: isValue, + year: isValue, }; diff --git a/src/DateInput/DayInput.spec.jsx b/src/DateInput/DayInput.spec.jsx index 86378fab..102133aa 100644 --- a/src/DateInput/DayInput.spec.jsx +++ b/src/DateInput/DayInput.spec.jsx @@ -120,7 +120,7 @@ describe('DayInput', () => { }); it('displays given value properly', () => { - const value = 11; + const value = '11'; const component = mount( 0), @@ -111,12 +144,19 @@ export default function Input({ disabled={disabled} inputMode="numeric" max={max} + maxLength={`${max}`.length} min={min} name={name} onChange={onChange} onFocus={onFocus} - onKeyDown={onKeyDown} - onKeyPress={makeOnKeyPress(maxLength)} + onKeyDown={(event) => { + onKeyDownInternal(event); + + if (onKeyDown) { + onKeyDown(event); + } + }} + onKeyPress={onKeyPressInternal} onKeyUp={(event) => { updateInputWidth(event.target); @@ -137,12 +177,14 @@ export default function Input({ }} required={required} step={step} - type="number" + type="text" value={value !== null ? value : ''} />, ]; } +const isValue = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); + Input.propTypes = { ariaLabel: PropTypes.string, autoFocus: PropTypes.bool, @@ -160,5 +202,5 @@ Input.propTypes = { required: PropTypes.bool, showLeadingZeros: PropTypes.bool, step: PropTypes.number, - value: PropTypes.number, + value: isValue, }; diff --git a/src/DateInput/MonthInput.jsx b/src/DateInput/MonthInput.jsx index 41865085..3db25da3 100644 --- a/src/DateInput/MonthInput.jsx +++ b/src/DateInput/MonthInput.jsx @@ -14,7 +14,7 @@ export default function MonthInput({ ...otherProps }) { function isSameYear(date) { - return date && year === getYear(date); + return date && Number(year) === getYear(date); } const maxMonth = safeMin(12, isSameYear(maxDate) && getMonthHuman(maxDate)); @@ -30,6 +30,8 @@ export default function MonthInput({ ); } +const isValue = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); + MonthInput.propTypes = { ariaLabel: PropTypes.string, className: PropTypes.string.isRequired, @@ -43,6 +45,6 @@ MonthInput.propTypes = { placeholder: PropTypes.string, required: PropTypes.bool, showLeadingZeros: PropTypes.bool, - value: PropTypes.number, - year: PropTypes.number, + value: isValue, + year: isValue, }; diff --git a/src/DateInput/MonthInput.spec.jsx b/src/DateInput/MonthInput.spec.jsx index 3210b50c..30d90a9e 100644 --- a/src/DateInput/MonthInput.spec.jsx +++ b/src/DateInput/MonthInput.spec.jsx @@ -120,7 +120,7 @@ describe('MonthInput', () => { }); it('displays given value properly', () => { - const value = 11; + const value = '11'; const component = mount( { }); it('displays given value properly', () => { - const value = 11; + const value = '11'; const component = mount( { }); it('displays given value properly', () => { - const value = 2018; + const value = '2018'; const component = mount(