UNPKG

6.66 kBJSXView Raw
1/**
2 * apeman react package text component.
3 * @class ApText
4 */
5
6'use strict'
7
8import React, { Component, PropTypes as types } from 'react'
9import ReactDOM from 'react-dom'
10import classnames from 'classnames'
11
12/** @lends ApText */
13class ApText extends Component {
14 // --------------------
15 // Specs
16 // --------------------
17 constructor (props) {
18 super(props)
19 const s = this
20 s.state = {
21 suggesting: false,
22 candidates: null,
23 selectedCandidate: null
24 }
25 let methodsToBind = [
26 'handleFocus',
27 'handleKeyUp',
28 'handleChange',
29 'handleBlur',
30 'handleKeyDown',
31 'handleCandidate'
32 ]
33 for (let name of methodsToBind) {
34 s[ name ] = s[ name ].bind(s)
35 }
36 }
37
38 render () {
39 const s = this
40 let { state, props } = s
41 let {
42 id,
43 name,
44 placeholder,
45 autoFocus,
46 className,
47 value,
48 rows
49 } = props
50 let hasVal = !!value
51
52 let multiline = rows && (rows > 1)
53
54 let {
55 candidates,
56 selectedCandidate,
57 suggesting
58 } = state
59
60 let textHandlers = {
61 onFocus: s.handleFocus,
62 onKeyUp: s.handleKeyUp,
63 onChange: s.handleChange,
64 onBlur: s.handleBlur,
65 onKeyDown: s.handleKeyDown
66 }
67 let candidateHandlers = {
68 onClick: s.handleCandidate
69 }
70
71 return (
72 <span className={ classnames('ap-text-wrap', { 'ap-text-wrap-empty': !hasVal }) }>
73 <ApText.Text { ...{ id, name, value, placeholder, className, autoFocus, multiline, rows } }
74 handlers={ textHandlers }
75 />
76 <ApText.CandidateList { ...{ suggesting, candidates, selectedCandidate, multiline } }
77 handlers={ candidateHandlers }
78 />
79 </span>
80 )
81 }
82
83 // --------------------
84 // Custom
85 // --------------------
86
87 handleCandidate (e) {
88 const s = this
89 let { props } = s
90 e.target.value = e.target.value || e.target.dataset.value
91 s.setState({ suggesting: false })
92 if (props.onChange) {
93 props.onChange(e)
94 }
95 }
96
97 handleFocus (e) {
98 const s = this
99 let { props } = s
100 s.setState({ suggesting: true })
101 s.updateCandidates()
102 if (props.onFocus) {
103 props.onFocus(e)
104 }
105 }
106
107 handleChange (e) {
108 const s = this
109 let { props } = s
110 s.setState({ suggesting: true })
111 if (props.onChange) {
112 props.onChange(e)
113 }
114 }
115
116 handleBlur (e) {
117 const s = this
118 let { props } = s
119 if (props.onBlur) {
120 props.onBlur(e)
121 }
122 }
123
124 handleKeyUp (e) {
125 const s = this
126 let { props } = s
127 s.updateCandidates()
128 if (props.onKeyUp) {
129 props.onKeyUp(e)
130 }
131 }
132
133 handleKeyDown (e) {
134 const s = this
135 let { props } = s
136 switch (e.keyCode) {
137 case 38: // UP
138 s.moveCandidateIndex(-1)
139 break
140 case 40: // DOWN
141 s.moveCandidateIndex(+1)
142 break
143 case 13: // Enter
144 s.enterCandidate()
145 break
146 default:
147 s.setState({ suggesting: true })
148 break
149 }
150 if (props.onKeyDown) {
151 props.onKeyDown(e)
152 }
153 }
154
155 moveCandidateIndex (amount) {
156 const s = this
157 let { candidates, selectedCandidate } = s.state
158 if (!candidates) {
159 return
160 }
161 let index = candidates.indexOf(selectedCandidate) + amount
162 let over = (index === -1) || (index === candidates.length)
163 if (over) {
164 return
165 }
166 s.setState({
167 selectedCandidate: candidates[ index ] || null
168 })
169 }
170
171 updateCandidates () {
172 const s = this
173 let { props } = s
174 let { value } = props
175 let candidates = (props.candidates || [])
176 .filter((candidate) => !!candidate)
177 .map((candidate) => String(candidate).trim())
178 .filter((candidate) => !value || candidate.match(value))
179
180 let hit = (candidates.length === 1) && (candidates[ 0 ] === value)
181 if (hit) {
182 candidates = null
183 }
184 s.setState({ candidates })
185 }
186
187 enterCandidate () {
188 const s = this
189 let { props } = s
190 let { candidates, selectedCandidate } = s.state
191 let valid = candidates && !!~candidates.indexOf(selectedCandidate)
192 if (valid) {
193 let target = { value: selectedCandidate }
194 if (props.onChange) {
195 props.onChange({ target })
196 }
197 s.setState({ suggesting: false })
198 }
199 }
200
201 // --------------------
202 // Static methods
203 // --------------------
204 static Text ({ id, name, value, placeholder, className, autoFocus, multiline, handlers, rows }) {
205 if (multiline) {
206 return (
207 <textarea autoFocus={ autoFocus }
208 id={ id }
209 name={ name }
210 rows={ rows }
211 placeholder={ placeholder }
212 className={ classnames('ap-text ap-text-multiple', className) }
213 value={ value }
214 { ...handlers }
215 onFocus={ null }
216 >
217 </textarea>
218 )
219 } else {
220 return (
221 <input autoFocus={ autoFocus }
222 id={ id }
223 name={ name }
224 placeholder={ placeholder }
225 className={ classnames('ap-text', className)}
226 value={ value }
227 { ...handlers }
228 type='text'
229 />
230 )
231 }
232 }
233
234 static CandidateList ({ suggesting, candidates, selectedCandidate, multiline, handlers }) {
235 if (!suggesting) {
236 return null
237 }
238 if (multiline) {
239 console.warn('[ApText] Can not use candidates with multiline input.')
240 return null
241 }
242
243 if (!candidates) {
244 return null
245 }
246
247 if (!candidates.length) {
248 return null
249 }
250
251 return (
252 <ul className='ap-text-candidate-list'>
253 {
254 candidates.map((candidate) =>
255 <li key={ candidate }
256 className={ classnames('ap-text-candidate-list-item', {
257 'ap-text-candidate-list-item-selected': candidate === selectedCandidate
258 }) }>
259 <a { ...handlers }
260 data-value={ candidate }>{ candidate }</a>
261 </li>
262 )
263 }
264 </ul>
265 )
266 }
267}
268
269Object.assign(ApText, {
270 // --------------------
271 // Specs
272 // --------------------
273
274 propTypes: {
275 /** Name of text input */
276 name: types.string,
277 /** Value of text input */
278 value: types.string,
279 /** Placeholder text */
280 placeholder: types.string,
281 /** Number of rows */
282 rows: types.number,
283 /** Selectable candidate text */
284 candidates: types.arrayOf(types.string)
285 },
286
287 defaultProps: {
288 name: '',
289 value: '',
290 placeholder: '',
291 rows: 1,
292 candidates: null
293 }
294
295})
296
297export default ApText