]> git.immae.eu Git - github/fretlink/time-picker.git/blob - src/Header.jsx
add test that covers focus on open code
[github/fretlink/time-picker.git] / src / Header.jsx
1 import React, { Component } from 'react';
2 import PropTypes from 'prop-types';
3 import moment from 'moment';
4
5 class Header extends Component {
6 static propTypes = {
7 format: PropTypes.string,
8 prefixCls: PropTypes.string,
9 disabledDate: PropTypes.func,
10 placeholder: PropTypes.string,
11 clearText: PropTypes.string,
12 value: PropTypes.object,
13 hourOptions: PropTypes.array,
14 minuteOptions: PropTypes.array,
15 secondOptions: PropTypes.array,
16 disabledHours: PropTypes.func,
17 disabledMinutes: PropTypes.func,
18 disabledSeconds: PropTypes.func,
19 onChange: PropTypes.func,
20 onClear: PropTypes.func,
21 onEsc: PropTypes.func,
22 allowEmpty: PropTypes.bool,
23 defaultOpenValue: PropTypes.object,
24 currentSelectPanel: PropTypes.string,
25 focusOnOpen: PropTypes.bool,
26 };
27
28 constructor(props) {
29 super(props);
30 const { value, format } = props;
31 this.state = {
32 str: value && value.format(format) || '',
33 invalid: false,
34 };
35 }
36
37 componentDidMount() {
38 if (this.props.focusOnOpen) {
39 // Wait one frame for the panel to be positioned before focusing
40 const requestAnimationFrame = (window.requestAnimationFrame || window.setTimeout);
41 requestAnimationFrame(() => {
42 this.refs.input.focus();
43 this.refs.input.select();
44 });
45 }
46 }
47
48 componentWillReceiveProps(nextProps) {
49 const { value, format } = nextProps;
50 this.setState({
51 str: value && value.format(format) || '',
52 invalid: false,
53 });
54 }
55
56 onInputChange = (event) => {
57 const str = event.target.value;
58 this.setState({
59 str,
60 });
61 const {
62 format, hourOptions, minuteOptions, secondOptions,
63 disabledHours, disabledMinutes,
64 disabledSeconds, onChange, allowEmpty,
65 } = this.props;
66
67 if (str) {
68 const originalValue = this.props.value;
69 const value = this.getProtoValue().clone();
70 const parsed = moment(str, format, true);
71 if (!parsed.isValid()) {
72 this.setState({
73 invalid: true,
74 });
75 return;
76 }
77 value.hour(parsed.hour()).minute(parsed.minute()).second(parsed.second());
78
79 // if time value not allowed, response warning.
80 if (
81 hourOptions.indexOf(value.hour()) < 0 ||
82 minuteOptions.indexOf(value.minute()) < 0 ||
83 secondOptions.indexOf(value.second()) < 0
84 ) {
85 this.setState({
86 invalid: true,
87 });
88 return;
89 }
90
91 // if time value is disabled, response warning.
92 const disabledHourOptions = disabledHours();
93 const disabledMinuteOptions = disabledMinutes(value.hour());
94 const disabledSecondOptions = disabledSeconds(value.hour(), value.minute());
95 if (
96 (disabledHourOptions && disabledHourOptions.indexOf(value.hour()) >= 0) ||
97 (disabledMinuteOptions && disabledMinuteOptions.indexOf(value.minute()) >= 0) ||
98 (disabledSecondOptions && disabledSecondOptions.indexOf(value.second()) >= 0)
99 ) {
100 this.setState({
101 invalid: true,
102 });
103 return;
104 }
105
106 if (originalValue) {
107 if (
108 originalValue.hour() !== value.hour() ||
109 originalValue.minute() !== value.minute() ||
110 originalValue.second() !== value.second()
111 ) {
112 // keep other fields for rc-calendar
113 const changedValue = originalValue.clone();
114 changedValue.hour(value.hour());
115 changedValue.minute(value.minute());
116 changedValue.second(value.second());
117 onChange(changedValue);
118 }
119 } else if (originalValue !== value) {
120 onChange(value);
121 }
122 } else if (allowEmpty) {
123 onChange(null);
124 } else {
125 this.setState({
126 invalid: true,
127 });
128 return;
129 }
130
131 this.setState({
132 invalid: false,
133 });
134 }
135
136 onKeyDown = (e) => {
137 if (e.keyCode === 27) {
138 this.props.onEsc();
139 }
140 }
141
142 onClear = () => {
143 this.setState({ str: '' });
144 this.props.onClear();
145 }
146
147 getClearButton() {
148 const { prefixCls, allowEmpty } = this.props;
149 if (!allowEmpty) {
150 return null;
151 }
152 return (<a
153 className={`${prefixCls}-clear-btn`}
154 role="button"
155 title={this.props.clearText}
156 onMouseDown={this.onClear}
157 />);
158 }
159
160 getProtoValue() {
161 return this.props.value || this.props.defaultOpenValue;
162 }
163
164 getInput() {
165 const { prefixCls, placeholder } = this.props;
166 const { invalid, str } = this.state;
167 const invalidClass = invalid ? `${prefixCls}-input-invalid` : '';
168 return (
169 <input
170 className={`${prefixCls}-input ${invalidClass}`}
171 ref="input"
172 onKeyDown={this.onKeyDown}
173 value={str}
174 placeholder={placeholder}
175 onChange={this.onInputChange}
176 />
177 );
178 }
179
180 render() {
181 const { prefixCls } = this.props;
182 return (
183 <div className={`${prefixCls}-input-wrap`}>
184 {this.getInput()}
185 {this.getClearButton()}
186 </div>
187 );
188 }
189 }
190
191 export default Header;