react-select-input-v2 1.0.7 → 1.0.8-react16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,30 +1,29 @@
1
1
  {
2
2
  "name": "react-select-input-v2",
3
- "version": "1.0.7",
4
- "description": "react-select-input React component",
3
+ "version": "1.0.8-react16",
4
+ "description": "react-select-input React component (React 16 compatible)",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
7
7
  "files": [
8
8
  "css",
9
9
  "es",
10
10
  "lib",
11
- "umd"
11
+ "umd",
12
+ "src"
12
13
  ],
13
14
  "scripts": {
14
15
  "build": "nwb build-react-component --copy-files",
15
16
  "clean": "nwb clean-module && nwb clean-demo",
16
17
  "start": "nwb serve-react-demo",
17
- "test": "nwb test-react",
18
- "test:coverage": "nwb test-react --coverage",
19
- "test:watch": "nwb test-react --server",
20
- "deploy": "gh-pages -d demo/dist"
18
+ "test": "nwb test-react"
21
19
  },
22
20
  "dependencies": {
23
21
  "prop-types": "^15.6.2",
24
22
  "react-autosize-textarea": "^0.4.8"
25
23
  },
26
24
  "peerDependencies": {
27
- "react": "15.x"
25
+ "react": "^15.0.0 || ^16.0.0",
26
+ "react-dom": "^15.0.0 || ^16.0.0"
28
27
  },
29
28
  "devDependencies": {
30
29
  "eslint": "^5.4.0",
@@ -32,38 +31,14 @@
32
31
  "eslint-plugin-import": "^2.14.0",
33
32
  "eslint-plugin-prettier": "^2.6.2",
34
33
  "eslint-plugin-react": "^7.11.1",
35
- "gh-pages": "^1.0.0",
36
- "nwb": "0.18.x",
37
- "prettier": "^1.14.2",
38
- "react": "^15.6.1",
39
- "react-dom": "^15.6.1",
40
- "babel-eslint": "^8.2.6"
34
+ "nwb": "^0.18.10",
35
+ "react": "^16.14.0",
36
+ "react-dom": "^16.14.0"
41
37
  },
42
- "author": "Samrith Shankar",
43
- "homepage": "https://samrith-s.github.io/react-select-input",
38
+ "author": "Samrith Shankar (modified by you)",
44
39
  "license": "MIT",
45
40
  "repository": {
46
41
  "type": "git",
47
- "url": "git+https://github.com/samrith-s/react-select-input.git"
48
- },
49
- "keywords": [
50
- "react-component",
51
- "input",
52
- "select",
53
- "input select combo",
54
- "select dropdown",
55
- "custom select",
56
- "searchable",
57
- "searchable select",
58
- "free input",
59
- "free select"
60
- ],
61
- "sideEffects": false,
62
- "bugs": {
63
- "url": "https://github.com/samrith-s/react-select-input/issues"
64
- },
65
- "directories": {
66
- "lib": "lib",
67
- "test": "tests"
42
+ "url": "local"
68
43
  }
69
44
  }
package/src/index.js ADDED
@@ -0,0 +1,270 @@
1
+ import React, { Component } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import Textarea from 'react-autosize-textarea';
4
+
5
+ function escapeRegExp(string) {
6
+ const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
7
+ const reHasRegExpChar = RegExp(reRegExpChar.source);
8
+ return string && reHasRegExpChar.test(string) ? string.replace(reRegExpChar, '\\$&') : string || '';
9
+ }
10
+
11
+ export default class SelectInput extends Component {
12
+ constructor() {
13
+ super();
14
+ this.state = {
15
+ isOpen: false,
16
+ value: null,
17
+ currentOption: -1,
18
+ selectedOption: null,
19
+ searchMatchOptions: []
20
+ };
21
+ }
22
+
23
+ componentDidMount() {
24
+ document.addEventListener('click', this.handleOutsideClick);
25
+ }
26
+
27
+ componentDidUpdate() {
28
+ if (
29
+ this.props.openUp &&
30
+ this.state.currentOption === -1 &&
31
+ this.state.isOpen &&
32
+ !this.state.selectedOption
33
+ ) {
34
+ if (this.options) this.options.scrollTop = this.options.scrollHeight;
35
+ } else if (
36
+ this.props.openUp &&
37
+ this.state.selectedOption &&
38
+ this.state.isOpen
39
+ ) {
40
+ const ref = this['option-' + this.state.selectedOption];
41
+ if (ref && ref.scrollIntoViewIfNeeded) ref.scrollIntoViewIfNeeded(true);
42
+ }
43
+ }
44
+
45
+ componentWillUnmount() {
46
+ document.removeEventListener('click', this.handleOutsideClick);
47
+ }
48
+
49
+ // ======= 渲染选项 =======
50
+ renderOptions = options => {
51
+ return options && options.length > 0
52
+ ? options.map((option, index) => {
53
+ return (
54
+ <div
55
+ className={
56
+ 'ris-option' +
57
+ (this.state.selectedOption === option[this.props.valueKey] ? ' selected' : '') +
58
+ (this.state.currentOption === index ? ' current' : '')
59
+ }
60
+ key={`${this.props.uniqueKey}-option-${index}`}
61
+ onClick={this.handleClick.bind(this, index)}
62
+ ref={ref => (this['option-' + option[this.props.valueKey]] = ref)}
63
+ >
64
+ {option[this.props.labelKey]}
65
+ </div>
66
+ );
67
+ })
68
+ : null;
69
+ };
70
+
71
+ // ======= 事件处理 =======
72
+ handleSelect = (option, index) => {
73
+ let state = this.manipState(this.state, 'value', option[this.props.labelKey]);
74
+ state = this.manipState(state, 'selectedOption', option[this.props.valueKey]);
75
+ state = this.manipState(state, 'currentOption', -1);
76
+ if (this.props.collapseOnSelect) state = this.setIsOpen(state, false);
77
+ state = this.manipState(state, 'searchMatchOptions', this.matchingOptions(this.props.options, state.value));
78
+ this.setState(state);
79
+ if (this.isFunction(this.props.onSelect)) this.props.onSelect(option);
80
+ };
81
+
82
+ handleClick = (index, event) => {
83
+ const option = this.pickOption(index);
84
+ this.handleSelect(option, index);
85
+ };
86
+
87
+ handleChange = event => {
88
+ let state = this.manipState(this.state, 'value', event.target.value);
89
+ state = this.manipState(state, 'searchMatchOptions', this.matchingOptions(this.props.options, event.target.value));
90
+ state = this.setIsOpen(state, true);
91
+ if (!state.value) {
92
+ state = this.manipState(state, 'currentOption', -1);
93
+ state = this.manipState(state, 'selectedOption', null);
94
+ }
95
+ if (this.isFunction(this.props.onChange)) this.props.onChange(event);
96
+ this.setState(state);
97
+ };
98
+
99
+ handleFocus = event => {
100
+ let state = this.setIsOpen(this.state, true);
101
+ state = this.manipState(state, 'searchMatchOptions', this.matchingOptions(this.props.options, event.target.value));
102
+ this.setState(state);
103
+ if (this.isFunction(this.props.onFocus)) this.props.onFocus(event);
104
+ };
105
+
106
+ handleClear = () => {
107
+ let value = '';
108
+ let state = this.manipState(this.state, 'value', value);
109
+ state = this.manipState(state, 'searchMatchOptions', this.matchingOptions(this.props.options, state.value));
110
+ state = this.manipState(state, 'currentOption', -1);
111
+ state = this.manipState(state, 'selectedOption', null);
112
+ this.setState(state);
113
+ if (this.isFunction(this.props.onClear)) this.props.onClear();
114
+ };
115
+
116
+ handleBlur = event => {
117
+ if (this.props.collapseOnBlur) {
118
+ this.setState(this.setIsOpen(this.state, false));
119
+ }
120
+ if (this.isFunction(this.props.onBlur)) this.props.onBlur(event);
121
+ };
122
+
123
+ handleKeyUp = event => {
124
+ if (event.key === 'Enter' && this.state.currentOption > -1) this.handleSelect(this.pickOption());
125
+ if (event.key === 'Escape' && this.props.collapseOnEscape) this.setState(this.setIsOpen(this.state, false));
126
+ if (this.isFunction(this.props.onKeyUp)) this.props.onKeyUp(event);
127
+ };
128
+
129
+ handleKeyDown = event => {
130
+ if (event.key === 'Enter' && this.props.disableEnter) event.preventDefault();
131
+ if (event.key === 'Escape' && this.props.collapseOnEscape) this.setState(this.setIsOpen(this.state, false));
132
+ if (this.isFunction(this.props.onKeyDown)) this.props.onKeyDown(event);
133
+ this.handleOptionNavigation(event);
134
+ };
135
+
136
+ handleOptionNavigation = event => {
137
+ let state = {};
138
+ let currentOption = this.state.currentOption;
139
+
140
+ if (event.key === 'ArrowUp' || event.key === 'ArrowDown') event.preventDefault();
141
+
142
+ if (event.key === 'ArrowUp' && currentOption === -1 && this.props.openUp) currentOption = this.state.searchMatchOptions.length;
143
+
144
+ if (this.props.openUp) {
145
+ if (event.key === 'ArrowDown') currentOption = currentOption > -1 ? currentOption + 1 : -1;
146
+ else if (event.key === 'ArrowUp') currentOption--;
147
+ } else {
148
+ if (event.key === 'ArrowDown') currentOption++;
149
+ else if (event.key === 'ArrowUp') currentOption--;
150
+ }
151
+
152
+ if (currentOption < -1) currentOption = -1;
153
+ else if (currentOption > this.state.searchMatchOptions.length - 1) currentOption = this.state.searchMatchOptions.length - 1;
154
+
155
+ state = this.manipState(this.state, 'currentOption', currentOption);
156
+
157
+ const optionRef = currentOption > -1 ? this['option-' + this.state.searchMatchOptions[currentOption][this.props.valueKey]] : null;
158
+ if (optionRef) optionRef.scrollIntoViewIfNeeded(false);
159
+
160
+ this.setState(state);
161
+ };
162
+
163
+ matchingOptions = (options, value) => {
164
+ value = escapeRegExp(value);
165
+ let searchValue = value.replace(/\s/g, '');
166
+ let searchOptions = [];
167
+
168
+ if (options && options.length > 0 && value)
169
+ searchOptions = options.filter(option => {
170
+ let val = option[this.props.valueKey] || '';
171
+ let label = option[this.props.labelKey] || '';
172
+ let regexp = new RegExp(searchValue, 'gi');
173
+ return regexp.test(label.replace(/\s/g, '')) || regexp.test(val.replace(/\s/g, ''));
174
+ });
175
+ else searchOptions = options.slice();
176
+
177
+ if (this.props.openUp) searchOptions.reverse();
178
+
179
+ return searchOptions;
180
+ };
181
+
182
+ pickOption = index => {
183
+ index = index !== null && index !== undefined ? index : this.state.currentOption;
184
+ if (index > -1) return this.state.searchMatchOptions[index];
185
+ else return { [this.props.labelKey]: this.input.value.trim(), [this.props.valueKey]: this.input.value.trim() };
186
+ };
187
+
188
+ setIsOpen = (state, show) => ({ ...state, isOpen: show || false });
189
+ manipState = (state, key, value) => ({ ...state, [key]: value });
190
+ isFunction = obj => typeof obj === 'function';
191
+
192
+ handleOutsideClick = event => {
193
+ if (this.ris && !this.ris.contains(event.target) && this.props.collapseOnBlur) this.setState({ isOpen: false });
194
+ };
195
+
196
+ render() {
197
+ return (
198
+ <div
199
+ className={
200
+ 'ris' +
201
+ (this.props.openUp ? ' ris-open-up' : '') +
202
+ (this.props.clearable ? ' ris-is-clearable' : '') +
203
+ (this.props.className ? ' ' + this.props.className : '')
204
+ }
205
+ key={'ris-' + this.props.uniqueKey}
206
+ ref={ref => (this.ris = ref)}
207
+ style={this.props.style}
208
+ >
209
+ <Textarea
210
+ className="ris-input"
211
+ value={this.state.value !== null ? this.state.value : this.props.value}
212
+ placeholder={this.props.placeholder}
213
+ onChange={this.handleChange}
214
+ onFocus={this.handleFocus}
215
+ onBlur={this.props.onBlur}
216
+ onKeyUp={this.handleKeyUp}
217
+ onKeyDown={this.handleKeyDown}
218
+ autoFocus={this.props.autoFocus}
219
+ innerRef={ref => (this.input = ref)}
220
+ />
221
+ {this.props.clearable && <div className="ris-clearable" onClick={this.handleClear}>x</div>}
222
+ {this.state.isOpen && this.state.searchMatchOptions.length > 0 && (
223
+ <div className="ris-options" ref={ref => (this.options = ref)}>
224
+ {this.renderOptions(this.state.searchMatchOptions)}
225
+ </div>
226
+ )}
227
+ </div>
228
+ );
229
+ }
230
+ }
231
+
232
+ SelectInput.defaultProps = {
233
+ uniqueKey: 'react-select-input',
234
+ value: '',
235
+ valueKey: 'value',
236
+ labelKey: 'label',
237
+ placeholder: 'Enter text',
238
+ openUp: false,
239
+ disableEnter: true,
240
+ collapseOnBlur: true,
241
+ collapseOnEscape: true,
242
+ collapseOnSelect: true,
243
+ autoFocus: true,
244
+ clearable: true,
245
+ options: []
246
+ };
247
+
248
+ SelectInput.propTypes = {
249
+ uniqueKey: PropTypes.string,
250
+ value: PropTypes.string,
251
+ valueKey: PropTypes.string,
252
+ labelKey: PropTypes.string,
253
+ placeholder: PropTypes.string,
254
+ openUp: PropTypes.bool,
255
+ disableEnter: PropTypes.bool,
256
+ collapseOnBlur: PropTypes.bool,
257
+ collapseOnEscape: PropTypes.bool,
258
+ collapseOnSelect: PropTypes.bool,
259
+ autoFocus: PropTypes.bool,
260
+ clearable: PropTypes.bool,
261
+ options: PropTypes.array,
262
+ onChange: PropTypes.func,
263
+ onSelect: PropTypes.func,
264
+ onFocus: PropTypes.func,
265
+ onBlur: PropTypes.func,
266
+ onKeyUp: PropTypes.func,
267
+ onKeyDown: PropTypes.func,
268
+ onClear: PropTypes.func,
269
+ noOptions: PropTypes.node
270
+ };
@@ -0,0 +1,92 @@
1
+
2
+
3
+ .ris {
4
+ display: block;
5
+ position: relative;
6
+ box-sizing: border-box;
7
+ font-size:12px;
8
+ }
9
+
10
+ .ris * {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ .ris.ris-is-clearable .ris-clearable {
15
+ position: absolute;
16
+ top: 50%;
17
+ right: 5px;
18
+ font-size:1.1em;
19
+ margin-top:-1px;
20
+ transform: translate(0, -50%);
21
+ padding:5px;
22
+ color: #555;
23
+ cursor:pointer;
24
+ }
25
+
26
+ .ris.ris-is-creatable .ris-input {
27
+ padding-right:15px;
28
+ }
29
+
30
+ .ris .ris-input {
31
+ display: block;
32
+ resize: none;
33
+ width: 100%;
34
+ padding: 5px;
35
+ border: 5px solid #eee;
36
+ border-radius: 5px;
37
+ font-size:inherit;
38
+ }
39
+
40
+ .ris .ris-input:active, .ris .ris-input:focus {
41
+ outline: 0;
42
+ border: 5px solid #1e88e5;
43
+ }
44
+
45
+ .ris.ris-open-up .ris-options {
46
+ top:0;
47
+ transform:translate(-50%,-100%);
48
+ }
49
+
50
+ .ris .ris-options {
51
+ position: absolute;
52
+ top: 100%;
53
+ left: 50%;
54
+ margin:0;
55
+ width: calc(100% - 10px);
56
+ background: #fff;
57
+ transform:translate(-50%, 0);
58
+ border: 1px solid #eee;
59
+ overflow: auto;
60
+ box-shadow: 0 2px 3px 1px #eee;
61
+ max-height:245px;
62
+ font-size:inherit;
63
+ }
64
+
65
+ .ris .ris-options .ris-option {
66
+ padding: 5px;
67
+ cursor: pointer;
68
+ border-bottom: 1px solid #efefef;
69
+ }
70
+
71
+ .ris .ris-options .ris-option:last-child {
72
+ border-bottom: 0;
73
+ }
74
+
75
+ .ris .ris-options .ris-option:hover {
76
+ background: #bbdefb;
77
+ }
78
+
79
+ .ris .ris-options .ris-option.current {
80
+ background:#bbdefb;
81
+ }
82
+
83
+ .ris .ris-options .ris-option.selected {
84
+ background:#1e88e5;
85
+ color: #fff;
86
+ }
87
+
88
+ .ris .ris-options.ris-no-options {
89
+ padding:5px;
90
+ text-align:center;
91
+ color:#ccc;
92
+ }