UNPKG

21.5 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports.getInitialState = getInitialState;
9exports.clearTypeahead = clearTypeahead;
10exports.hideMenu = hideMenu;
11exports.toggleMenu = toggleMenu;
12exports["default"] = void 0;
13
14var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
15
16var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
17
18var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose"));
19
20var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
21
22var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
23
24var _fastDeepEqual = _interopRequireDefault(require("fast-deep-equal"));
25
26var _propTypes = _interopRequireDefault(require("prop-types"));
27
28var _react = _interopRequireDefault(require("react"));
29
30var _TypeaheadManager = _interopRequireDefault(require("./TypeaheadManager"));
31
32var _propTypes2 = require("../propTypes");
33
34var _utils = require("../utils");
35
36var _constants = require("../constants");
37
38var propTypes = {
39 /**
40 * Allows the creation of new selections on the fly. Note that any new items
41 * will be added to the list of selections, but not the list of original
42 * options unless handled as such by `Typeahead`'s parent.
43 *
44 * If a function is specified, it will be used to determine whether a custom
45 * option should be included. The return value should be true or false.
46 */
47 allowNew: _propTypes["default"].oneOfType([_propTypes["default"].bool, _propTypes["default"].func]),
48
49 /**
50 * Autofocus the input when the component initially mounts.
51 */
52 autoFocus: _propTypes["default"].bool,
53
54 /**
55 * Whether or not filtering should be case-sensitive.
56 */
57 caseSensitive: (0, _propTypes2.checkPropType)(_propTypes["default"].bool, _propTypes2.caseSensitiveType),
58
59 /**
60 * The initial value displayed in the text input.
61 */
62 defaultInputValue: (0, _propTypes2.checkPropType)(_propTypes["default"].string, _propTypes2.defaultInputValueType),
63
64 /**
65 * Whether or not the menu is displayed upon initial render.
66 */
67 defaultOpen: _propTypes["default"].bool,
68
69 /**
70 * Specify any pre-selected options. Use only if you want the component to
71 * be uncontrolled.
72 */
73 defaultSelected: (0, _propTypes2.checkPropType)(_propTypes["default"].arrayOf(_propTypes2.optionType), _propTypes2.defaultSelectedType),
74
75 /**
76 * Either an array of fields in `option` to search, or a custom filtering
77 * callback.
78 */
79 filterBy: _propTypes["default"].oneOfType([_propTypes["default"].arrayOf(_propTypes["default"].string.isRequired), _propTypes["default"].func]),
80
81 /**
82 * Highlights the menu item if there is only one result and allows selecting
83 * that item by hitting enter. Does not work with `allowNew`.
84 */
85 highlightOnlyResult: (0, _propTypes2.checkPropType)(_propTypes["default"].bool, _propTypes2.highlightOnlyResultType),
86
87 /**
88 * An html id attribute, required for assistive technologies such as screen
89 * readers.
90 */
91 id: (0, _propTypes2.checkPropType)(_propTypes["default"].oneOfType([_propTypes["default"].number, _propTypes["default"].string]), _propTypes2.isRequiredForA11y),
92
93 /**
94 * Whether the filter should ignore accents and other diacritical marks.
95 */
96 ignoreDiacritics: (0, _propTypes2.checkPropType)(_propTypes["default"].bool, _propTypes2.ignoreDiacriticsType),
97
98 /**
99 * Specify the option key to use for display or a function returning the
100 * display string. By default, the selector will use the `label` key.
101 */
102 labelKey: (0, _propTypes2.checkPropType)(_propTypes["default"].oneOfType([_propTypes["default"].string, _propTypes["default"].func]), _propTypes2.labelKeyType),
103
104 /**
105 * Maximum number of results to display by default. Mostly done for
106 * performance reasons so as not to render too many DOM nodes in the case of
107 * large data sets.
108 */
109 maxResults: _propTypes["default"].number,
110
111 /**
112 * Number of input characters that must be entered before showing results.
113 */
114 minLength: _propTypes["default"].number,
115
116 /**
117 * Whether or not multiple selections are allowed.
118 */
119 multiple: _propTypes["default"].bool,
120
121 /**
122 * Invoked when the input is blurred. Receives an event.
123 */
124 onBlur: _propTypes["default"].func,
125
126 /**
127 * Invoked whenever items are added or removed. Receives an array of the
128 * selected options.
129 */
130 onChange: _propTypes["default"].func,
131
132 /**
133 * Invoked when the input is focused. Receives an event.
134 */
135 onFocus: _propTypes["default"].func,
136
137 /**
138 * Invoked when the input value changes. Receives the string value of the
139 * input.
140 */
141 onInputChange: _propTypes["default"].func,
142
143 /**
144 * Invoked when a key is pressed. Receives an event.
145 */
146 onKeyDown: _propTypes["default"].func,
147
148 /**
149 * Invoked when menu visibility changes.
150 */
151 onMenuToggle: _propTypes["default"].func,
152
153 /**
154 * Invoked when the pagination menu item is clicked. Receives an event.
155 */
156 onPaginate: _propTypes["default"].func,
157
158 /**
159 * Whether or not the menu should be displayed. `undefined` allows the
160 * component to control visibility, while `true` and `false` show and hide
161 * the menu, respectively.
162 */
163 open: _propTypes["default"].bool,
164
165 /**
166 * Full set of options, including pre-selected options. Must either be an
167 * array of objects (recommended) or strings.
168 */
169 options: _propTypes["default"].arrayOf(_propTypes2.optionType).isRequired,
170
171 /**
172 * Give user the ability to display additional results if the number of
173 * results exceeds `maxResults`.
174 */
175 paginate: _propTypes["default"].bool,
176
177 /**
178 * The selected option(s) displayed in the input. Use this prop if you want
179 * to control the component via its parent.
180 */
181 selected: (0, _propTypes2.checkPropType)(_propTypes["default"].arrayOf(_propTypes2.optionType), _propTypes2.selectedType),
182
183 /**
184 * Allows selecting the hinted result by pressing enter.
185 */
186 selectHintOnEnter: (0, _propTypes2.deprecated)(_propTypes["default"].bool, 'Use the `shouldSelect` prop on the `Hint` component to define which ' + 'keystrokes can select the hint.')
187};
188var defaultProps = {
189 allowNew: false,
190 autoFocus: false,
191 caseSensitive: false,
192 defaultInputValue: '',
193 defaultOpen: false,
194 defaultSelected: [],
195 filterBy: [],
196 highlightOnlyResult: false,
197 ignoreDiacritics: true,
198 labelKey: _constants.DEFAULT_LABELKEY,
199 maxResults: 100,
200 minLength: 0,
201 multiple: false,
202 onBlur: _utils.noop,
203 onFocus: _utils.noop,
204 onInputChange: _utils.noop,
205 onKeyDown: _utils.noop,
206 onMenuToggle: _utils.noop,
207 onPaginate: _utils.noop,
208 paginate: true
209};
210
211function getInitialState(props) {
212 var defaultInputValue = props.defaultInputValue,
213 defaultOpen = props.defaultOpen,
214 defaultSelected = props.defaultSelected,
215 maxResults = props.maxResults,
216 multiple = props.multiple;
217 var selected = props.selected ? props.selected.slice() : defaultSelected.slice();
218 var text = defaultInputValue;
219
220 if (!multiple && selected.length) {
221 // Set the text if an initial selection is passed in.
222 text = (0, _utils.getOptionLabel)((0, _utils.head)(selected), props.labelKey);
223
224 if (selected.length > 1) {
225 // Limit to 1 selection in single-select mode.
226 selected = selected.slice(0, 1);
227 }
228 }
229
230 return {
231 activeIndex: -1,
232 activeItem: null,
233 initialItem: null,
234 isFocused: false,
235 selected: selected,
236 showMenu: defaultOpen,
237 shownResults: maxResults,
238 text: text
239 };
240}
241
242function clearTypeahead(state, props) {
243 return (0, _extends2["default"])({}, getInitialState(props), {
244 isFocused: state.isFocused,
245 selected: [],
246 text: ''
247 });
248}
249
250function hideMenu(state, props) {
251 var _getInitialState = getInitialState(props),
252 activeIndex = _getInitialState.activeIndex,
253 activeItem = _getInitialState.activeItem,
254 initialItem = _getInitialState.initialItem,
255 shownResults = _getInitialState.shownResults;
256
257 return {
258 activeIndex: activeIndex,
259 activeItem: activeItem,
260 initialItem: initialItem,
261 showMenu: false,
262 shownResults: shownResults
263 };
264}
265
266function toggleMenu(state, props) {
267 return state.showMenu ? hideMenu(state, props) : {
268 showMenu: true
269 };
270}
271
272var Typeahead = /*#__PURE__*/function (_React$Component) {
273 (0, _inheritsLoose2["default"])(Typeahead, _React$Component);
274
275 function Typeahead() {
276 var _this;
277
278 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
279 args[_key] = arguments[_key];
280 }
281
282 _this = _React$Component.call.apply(_React$Component, [this].concat(args)) || this;
283 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "state", getInitialState(_this.props));
284 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "inputNode", void 0);
285 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "isMenuShown", false);
286 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "items", []);
287 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "blur", function () {
288 _this.inputNode && _this.inputNode.blur();
289
290 _this.hideMenu();
291 });
292 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "clear", function () {
293 _this.setState(clearTypeahead);
294 });
295 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "focus", function () {
296 _this.inputNode && _this.inputNode.focus();
297 });
298 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "getInput", function () {
299 return _this.inputNode;
300 });
301 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "inputRef", function (inputNode) {
302 _this.inputNode = inputNode;
303 });
304 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "setItem", function (item, position) {
305 _this.items[position] = item;
306 });
307 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "hideMenu", function () {
308 _this.setState(hideMenu);
309 });
310 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "toggleMenu", function () {
311 _this.setState(toggleMenu);
312 });
313 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleActiveIndexChange", function (activeIndex) {
314 _this.setState(function (state) {
315 return {
316 activeIndex: activeIndex,
317 activeItem: activeIndex === -1 ? null : state.activeItem
318 };
319 });
320 });
321 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleActiveItemChange", function (activeItem) {
322 // Don't update the active item if it hasn't changed.
323 if (!(0, _fastDeepEqual["default"])(activeItem, _this.state.activeItem)) {
324 _this.setState({
325 activeItem: activeItem
326 });
327 }
328 });
329 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleBlur", function (e) {
330 e.persist();
331
332 _this.setState({
333 isFocused: false
334 }, function () {
335 return _this.props.onBlur(e);
336 });
337 });
338 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleChange", function (selected) {
339 _this.props.onChange && _this.props.onChange(selected);
340 });
341 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleClear", function () {
342 _this.setState(clearTypeahead, function () {
343 return _this._handleChange([]);
344 });
345 });
346 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleFocus", function (e) {
347 e.persist();
348
349 _this.setState({
350 isFocused: true,
351 showMenu: true
352 }, function () {
353 return _this.props.onFocus(e);
354 });
355 });
356 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleInitialItemChange", function (initialItem) {
357 // Don't update the initial item if it hasn't changed.
358 if (!(0, _fastDeepEqual["default"])(initialItem, _this.state.initialItem)) {
359 _this.setState({
360 initialItem: initialItem
361 });
362 }
363 });
364 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleInputChange", function (e) {
365 e.persist();
366 var text = e.currentTarget.value;
367 var _this$props = _this.props,
368 multiple = _this$props.multiple,
369 onInputChange = _this$props.onInputChange; // Clear selections when the input value changes in single-select mode.
370
371 var shouldClearSelections = _this.state.selected.length && !multiple;
372
373 _this.setState(function (state, props) {
374 var _getInitialState2 = getInitialState(props),
375 activeIndex = _getInitialState2.activeIndex,
376 activeItem = _getInitialState2.activeItem,
377 shownResults = _getInitialState2.shownResults;
378
379 return {
380 activeIndex: activeIndex,
381 activeItem: activeItem,
382 selected: shouldClearSelections ? [] : state.selected,
383 showMenu: true,
384 shownResults: shownResults,
385 text: text
386 };
387 }, function () {
388 onInputChange(text, e);
389 shouldClearSelections && _this._handleChange([]);
390 });
391 });
392 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleKeyDown", function (e) {
393 var activeItem = _this.state.activeItem; // Skip most actions when the menu is hidden.
394
395 if (!_this.isMenuShown) {
396 if (e.keyCode === _constants.UP || e.keyCode === _constants.DOWN) {
397 _this.setState({
398 showMenu: true
399 });
400 }
401
402 _this.props.onKeyDown(e);
403
404 return;
405 }
406
407 switch (e.keyCode) {
408 case _constants.UP:
409 case _constants.DOWN:
410 // Prevent input cursor from going to the beginning when pressing up.
411 e.preventDefault();
412
413 _this._handleActiveIndexChange((0, _utils.getUpdatedActiveIndex)(_this.state.activeIndex, e.keyCode, _this.items));
414
415 break;
416
417 case _constants.RETURN:
418 // Prevent form submission while menu is open.
419 e.preventDefault();
420 activeItem && _this._handleMenuItemSelect(activeItem, e);
421 break;
422
423 case _constants.ESC:
424 case _constants.TAB:
425 // ESC simply hides the menu. TAB will blur the input and move focus to
426 // the next item; hide the menu so it doesn't gain focus.
427 _this.hideMenu();
428
429 break;
430
431 default:
432 break;
433 }
434
435 _this.props.onKeyDown(e);
436 });
437 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleMenuItemSelect", function (option, e) {
438 if (option.paginationOption) {
439 _this._handlePaginate(e);
440 } else {
441 _this._handleSelectionAdd(option);
442 }
443 });
444 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handlePaginate", function (e) {
445 e.persist();
446
447 _this.setState(function (state, props) {
448 return {
449 shownResults: state.shownResults + props.maxResults
450 };
451 }, function () {
452 return _this.props.onPaginate(e, _this.state.shownResults);
453 });
454 });
455 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleSelectionAdd", function (option) {
456 var _this$props2 = _this.props,
457 multiple = _this$props2.multiple,
458 labelKey = _this$props2.labelKey;
459 var selected;
460 var selection = option;
461 var text; // Add a unique id to the custom selection. Avoid doing this in `render` so
462 // the id doesn't increment every time.
463
464 if (!(0, _utils.isString)(selection) && selection.customOption) {
465 selection = (0, _extends2["default"])({}, selection, {
466 id: (0, _utils.uniqueId)('new-id-')
467 });
468 }
469
470 if (multiple) {
471 // If multiple selections are allowed, add the new selection to the
472 // existing selections.
473 selected = _this.state.selected.concat(selection);
474 text = '';
475 } else {
476 // If only a single selection is allowed, replace the existing selection
477 // with the new one.
478 selected = [selection];
479 text = (0, _utils.getOptionLabel)(selection, labelKey);
480 }
481
482 _this.setState(function (state, props) {
483 return (0, _extends2["default"])({}, hideMenu(state, props), {
484 initialItem: selection,
485 selected: selected,
486 text: text
487 });
488 }, function () {
489 return _this._handleChange(selected);
490 });
491 });
492 (0, _defineProperty2["default"])((0, _assertThisInitialized2["default"])(_this), "_handleSelectionRemove", function (selection) {
493 var selected = _this.state.selected.filter(function (option) {
494 return !(0, _fastDeepEqual["default"])(option, selection);
495 }); // Make sure the input stays focused after the item is removed.
496
497
498 _this.focus();
499
500 _this.setState(function (state, props) {
501 return (0, _extends2["default"])({}, hideMenu(state, props), {
502 selected: selected
503 });
504 }, function () {
505 return _this._handleChange(selected);
506 });
507 });
508 return _this;
509 }
510
511 var _proto = Typeahead.prototype;
512
513 _proto.componentDidMount = function componentDidMount() {
514 this.props.autoFocus && this.focus();
515 };
516
517 _proto.componentDidUpdate = function componentDidUpdate(prevProps, prevState) {
518 var _this$props3 = this.props,
519 labelKey = _this$props3.labelKey,
520 multiple = _this$props3.multiple,
521 selected = _this$props3.selected;
522 (0, _utils.validateSelectedPropChange)(selected, prevProps.selected); // Sync selections in state with those in props.
523
524 if (selected && !(0, _fastDeepEqual["default"])(selected, prevState.selected)) {
525 this.setState({
526 selected: selected
527 });
528
529 if (!multiple) {
530 this.setState({
531 text: selected.length ? (0, _utils.getOptionLabel)((0, _utils.head)(selected), labelKey) : ''
532 });
533 }
534 }
535 };
536
537 _proto.render = function render() {
538 // Omit `onChange` so Flow doesn't complain.
539 var _this$props4 = this.props,
540 onChange = _this$props4.onChange,
541 otherProps = (0, _objectWithoutPropertiesLoose2["default"])(_this$props4, ["onChange"]);
542 var mergedPropsAndState = (0, _extends2["default"])({}, otherProps, this.state);
543 var filterBy = mergedPropsAndState.filterBy,
544 labelKey = mergedPropsAndState.labelKey,
545 options = mergedPropsAndState.options,
546 paginate = mergedPropsAndState.paginate,
547 shownResults = mergedPropsAndState.shownResults,
548 text = mergedPropsAndState.text;
549 this.isMenuShown = (0, _utils.isShown)(mergedPropsAndState);
550 this.items = []; // Reset items on re-render.
551
552 var results = [];
553
554 if (this.isMenuShown) {
555 var cb = typeof filterBy === 'function' ? filterBy : _utils.defaultFilterBy;
556 results = options.filter(function (option) {
557 return cb(option, mergedPropsAndState);
558 }); // This must come before results are truncated.
559
560 var shouldPaginate = paginate && results.length > shownResults; // Truncate results if necessary.
561
562 results = (0, _utils.getTruncatedOptions)(results, shownResults); // Add the custom option if necessary.
563
564 if ((0, _utils.addCustomOption)(results, mergedPropsAndState)) {
565 var _results$push;
566
567 results.push((_results$push = {
568 customOption: true
569 }, _results$push[(0, _utils.getStringLabelKey)(labelKey)] = text, _results$push));
570 } // Add the pagination item if necessary.
571
572
573 if (shouldPaginate) {
574 var _results$push2;
575
576 results.push((_results$push2 = {}, _results$push2[(0, _utils.getStringLabelKey)(labelKey)] = '', _results$push2.paginationOption = true, _results$push2));
577 }
578 }
579
580 return /*#__PURE__*/_react["default"].createElement(_TypeaheadManager["default"], (0, _extends2["default"])({}, mergedPropsAndState, {
581 hideMenu: this.hideMenu,
582 inputNode: this.inputNode,
583 inputRef: this.inputRef,
584 isMenuShown: this.isMenuShown,
585 onActiveItemChange: this._handleActiveItemChange,
586 onAdd: this._handleSelectionAdd,
587 onBlur: this._handleBlur,
588 onChange: this._handleInputChange,
589 onClear: this._handleClear,
590 onFocus: this._handleFocus,
591 onHide: this.hideMenu,
592 onInitialItemChange: this._handleInitialItemChange,
593 onKeyDown: this._handleKeyDown,
594 onMenuItemClick: this._handleMenuItemSelect,
595 onRemove: this._handleSelectionRemove,
596 results: results,
597 setItem: this.setItem,
598 toggleMenu: this.toggleMenu
599 }));
600 };
601
602 return Typeahead;
603}(_react["default"].Component);
604
605(0, _defineProperty2["default"])(Typeahead, "propTypes", propTypes);
606(0, _defineProperty2["default"])(Typeahead, "defaultProps", defaultProps);
607var _default = Typeahead;
608exports["default"] = _default;
\No newline at end of file