UNPKG

react-date-picker

Version:

A carefully crafted date picker for React

428 lines (337 loc) 10.1 kB
import React, { PropTypes } from 'react' import { findDOMNode } from 'react-dom' import Component from 'react-class' import throttle from 'lodash.throttle' import assign from 'object-assign' import { getSelectionStart, getSelectionEnd, setCaretPosition } from '../TimeInput' import toMoment from '../toMoment'; import parseFormat from './parseFormat' import forwardTime from '../utils/forwardTime' const emptyFn = () => {} const BACKWARDS = { Backspace: 1, ArrowUp: 1, ArrowDown: 1, PageUp: 1, PageDown: 1 } export default class DateFormatInput extends Component { constructor(props) { super(props) const { positions, matches } = parseFormat(props.dateFormat) const defaultValue = props.defaultValue || Date.now() const delay = props.changeDelay this.throttleSetValue = delay == -1 ? this.setValue : throttle(this.setValue, delay) const { minDate, maxDate } = this.getMinMax(props) this.state = { positions, matches, propsValue: props.value !== undefined, value: defaultValue, minDate, maxDate } } getMinMax(props) { props = props || this.props let minDate = null if (props.minDate) { minDate = this.toMoment(props.minDate, props) } let maxDate = null if (props.maxDate) { maxDate = this.toMoment(props.maxDate, props) } return { minDate, maxDate } } componentWillReceiveProps(nextProps) { const { minDate, maxDate } = this.getMinMax(nextProps) this.setState({ minDate, maxDate }) } componentDidUpdate() { if (this.props.value !== undefined && this.caretPos && this.isFocused()) { this.setCaretPosition(this.caretPos) } } toMoment(value, props) { props = props || this.props return toMoment(value, { locale: props.locale, dateFormat: props.dateFormat || this.props.dateFormat }) } render() { const { props } = this const value = this.state.propsValue ? props.value : this.state.value const displayValue = this.displayValue = this.toMoment(value).format(props.dateFormat) const inputProps = assign({}, props) delete inputProps.changeDelay delete inputProps.date delete inputProps.dateFormat delete inputProps.isDateInput delete inputProps.maxDate delete inputProps.minDate delete inputProps.stopPropagation delete inputProps.updateOnWheel if (typeof props.cleanup == 'function') { props.cleanup(inputProps) } return <input {...inputProps} defaultValue={undefined} onFocus={this.onFocus} onBlur={this.onBlur} value={displayValue} onKeyDown={this.onKeyDown} onWheel={this.onWheel} onChange={this.onChange} /> } focus() { findDOMNode(this).focus() } onFocus(event) { if (this.props.onFocus) { this.props.onFocus(event) } this.setState({ focused: true }) } onBlur(event) { if (this.props.onBlur) { this.props.onBlur(event) } this.setState({ focused: false }) } isFocused() { return this.state.focused } onChange(event) { event.stopPropagation() } onDirection(dir, event = {}) { this.onKeyDown({ key: dir > 0 ? 'ArrowUp' : 'ArrowDown', type: event.type || 'unknown', stopPropagation: typeof event.stopPropagation == 'function' ? () => event.stopPropagation(): emptyFn, preventDefault: typeof event.preventDefault == 'function' ? () => event.preventDefault(): emptyFn }) } onWheel(event) { if (this.props.updateOnWheel && this.isFocused()) { this.onDirection(-event.deltaY, event) // this.onKeyDown({ // key: event.deltaY < 0 ? 'ArrowUp' : 'ArrowDown', // type: event.type, // stopPropagation: () => event.stopPropagation(), // preventDefault: () => event.preventDefault() // }) } if (this.props.onWheel) { this.props.onWheel(event) } } onKeyDown(event) { const { props } = this let { key, type, which } = event if (key !== 'Unidentified' && which && which >= 65 && which <= 90) { key = ' ' } if (key != ' ' && key * 1 == key) { key = 'Unidentified' } if (props.stopPropagation) { event.stopPropagation() } const range = this.getSelectedRange() const selectedValue = this.getSelectedValue(range) const value = this.displayValue const { positions, matches } = this.state const valueStr = `${value}` let currentPosition = positions[range.start] if (typeof currentPosition == 'string') { currentPosition = positions[range.start + (key in BACKWARDS ? -1 : 1)] } if (!currentPosition) { currentPosition = positions[range.start - 1] } if (props.onKeyDown && type == 'keydown') { if (props.onKeyDown(event, currentPosition) === false) { this.caretPos = range return } } let keyName = key if (key == 'ArrowUp' || key == 'ArrowDown') { keyName = 'Arrow' } const handlerName = `handle${keyName}` let preventDefault let newValue let newCaretPos if (currentPosition && currentPosition[handlerName]) { const returnValue = currentPosition[handlerName](currentPosition, { range, selectedValue, value, positions, currentValue: valueStr.substring(currentPosition.start, currentPosition.end + 1), matches, event, key, input: this.getInput(), setCaretPosition: (...args) => this.setCaretPosition(...args) }) this.caretPos = range if (returnValue && returnValue.value !== undefined) { newValue = valueStr.substring(0, currentPosition.start) + returnValue.value + valueStr.substring(currentPosition.end + 1) newCaretPos = returnValue.caretPos || range if (newCaretPos === true) { newCaretPos = { start: currentPosition.start, end: currentPosition.end + 1 } } preventDefault = returnValue.preventDefault !== false } } if (preventDefault || key == 'Backspace' || key == 'Delete' || key == ' ') { if (!preventDefault) { this.setCaretPosition(this.caretPos = { start: range.start + (key == 'Backspace' ? -1 : 1) }) } preventDefault = true } const config = { currentPosition, preventDefault, event, value: newValue, stop: false } if (this.props.afterKeyDown && type == 'keydown') { this.props.afterKeyDown(config) } if (!config.stop && newCaretPos !== undefined) { const updateCaretPos = () => this.setCaretPosition(newCaretPos) this.caretPos = newCaretPos this.setStateValue(newValue, updateCaretPos, { key, oldValue: valueStr, currentPosition }) } if (config.preventDefault) { event.preventDefault() } } getInput() { return findDOMNode(this) } setCaretPosition(pos) { const dom = this.getInput() if (dom) { setCaretPosition(dom, pos) } } format(mom, format) { return mom.format(format || this.props.dateFormat) } setStateValue(value, callback, { key, oldValue, currentPosition }) { let dateMoment = this.toMoment(value) if (!dateMoment.isValid()) { const dir = (key == 'ArrowUp' || key == 'PageUp') ? 1 : -1 if (currentPosition.format == 'MM') { // updating the month dateMoment = this.toMoment(oldValue).add(dir, 'month') } else { // updating the day dateMoment = dir > 0 ? // we've gone with +1 beyond max, so reset to 1 this.toMoment(oldValue).date(1) : // we've gone with -1 beyond max, so reset to max of month this.toMoment(oldValue).endOf('month') } if (!dateMoment.isValid()) { return; } value = this.format(dateMoment) } const { minDate, maxDate } = this.state if (minDate && dateMoment.isBefore(minDate)) { const clone = this.toMoment(dateMoment) // try with time dateMoment = forwardTime(clone, this.toMoment(minDate)) if (dateMoment.isBefore(minDate)) { // try without time dateMoment = this.toMoment(minDate) } value = this.format(dateMoment) } if (maxDate && dateMoment.isAfter(maxDate)) { const clone = this.toMoment(dateMoment) dateMoment = forwardTime(clone, this.toMoment(maxDate)) if (dateMoment.isAfter(maxDate)) { dateMoment = this.toMoment(maxDate) } value = this.format(dateMoment) } this.setState({ value, propsValue: false }, typeof callback == 'function' && callback) // if (this.props.value !== undefined) { if (this.props.onChange) { this.throttleSetValue(value, dateMoment) } } setValue(value, dateMoment) { if (this.props.value === undefined) { this.setState({ value, propsValue: false }) } else { this.setState({ propsValue: true, value: undefined }) } if (this.props.onChange) { this.props.onChange(value, { dateMoment: dateMoment || this.toMoment(value) }) } } getSelectedRange() { const dom = this.getInput() return { start: getSelectionStart(dom), end: getSelectionEnd(dom) } } getSelectedValue(range) { range = range || this.getSelectedRange() const value = this.displayValue return value.substring(range.start, range.end) } } DateFormatInput.defaultProps = { isDateInput: true, stopPropagation: true, updateOnWheel: true, changeDelay: 100 } DateFormatInput.propTypes = { dateFormat: PropTypes.string.isRequired, value: (props, propName) => { if (props[propName] !== undefined) { // console.warn('Due to performance considerations, TimeInput will only be uncontrolled.') } } }