UNPKG

14.6 kBJavaScriptView Raw
1"use strict";
2/*
3 * Copyright 2017 Palantir Technologies, Inc. All rights reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17Object.defineProperty(exports, "__esModule", { value: true });
18exports.TagInput = void 0;
19var tslib_1 = require("tslib");
20var classnames_1 = tslib_1.__importDefault(require("classnames"));
21var React = tslib_1.__importStar(require("react"));
22var common_1 = require("../../common");
23var props_1 = require("../../common/props");
24var utils_1 = require("../../common/utils");
25var icon_1 = require("../icon/icon");
26var tag_1 = require("../tag/tag");
27/** special value for absence of active tag */
28var NONE = -1;
29/**
30 * Tag input component.
31 *
32 * @see https://blueprintjs.com/docs/#core/components/tag-input
33 */
34var TagInput = /** @class */ (function (_super) {
35 tslib_1.__extends(TagInput, _super);
36 function TagInput() {
37 var _this = _super !== null && _super.apply(this, arguments) || this;
38 _this.state = {
39 activeIndex: NONE,
40 inputValue: _this.props.inputValue || "",
41 isInputFocused: false,
42 };
43 _this.inputElement = null;
44 _this.handleRef = (0, common_1.refHandler)(_this, "inputElement", _this.props.inputRef);
45 _this.addTags = function (value, method) {
46 if (method === void 0) { method = "default"; }
47 var _a = _this.props, inputValue = _a.inputValue, onAdd = _a.onAdd, onChange = _a.onChange, values = _a.values;
48 var newValues = _this.getValues(value);
49 var shouldClearInput = (onAdd === null || onAdd === void 0 ? void 0 : onAdd(newValues, method)) !== false && inputValue === undefined;
50 // avoid a potentially expensive computation if this prop is omitted
51 if (common_1.Utils.isFunction(onChange)) {
52 shouldClearInput = onChange(tslib_1.__spreadArray(tslib_1.__spreadArray([], values, true), newValues, true)) !== false && shouldClearInput;
53 }
54 // only explicit return false cancels text clearing
55 if (shouldClearInput) {
56 _this.setState({ inputValue: "" });
57 }
58 };
59 _this.maybeRenderTag = function (tag, index) {
60 if (!tag) {
61 return null;
62 }
63 var _a = _this.props, large = _a.large, tagProps = _a.tagProps;
64 var props = common_1.Utils.isFunction(tagProps) ? tagProps(tag, index) : tagProps;
65 return (React.createElement(tag_1.Tag, tslib_1.__assign({ active: index === _this.state.activeIndex, "data-tag-index": index, key: tag + "__" + index, large: large, onRemove: _this.props.disabled ? undefined : _this.handleRemoveTag }, props), tag));
66 };
67 _this.handleContainerClick = function () {
68 var _a;
69 (_a = _this.inputElement) === null || _a === void 0 ? void 0 : _a.focus();
70 };
71 _this.handleContainerBlur = function (_a) {
72 var currentTarget = _a.currentTarget;
73 _this.requestAnimationFrame(function () {
74 // we only care if the blur event is leaving the container.
75 // defer this check using rAF so activeElement will have updated.
76 var isFocusInsideContainer = currentTarget.contains((0, utils_1.getActiveElement)(_this.inputElement));
77 if (!isFocusInsideContainer) {
78 if (_this.props.addOnBlur && _this.state.inputValue !== undefined && _this.state.inputValue.length > 0) {
79 _this.addTags(_this.state.inputValue, "blur");
80 }
81 _this.setState({ activeIndex: NONE, isInputFocused: false });
82 }
83 });
84 };
85 _this.handleInputFocus = function (event) {
86 var _a, _b;
87 _this.setState({ isInputFocused: true });
88 (_b = (_a = _this.props.inputProps) === null || _a === void 0 ? void 0 : _a.onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, event);
89 };
90 _this.handleInputChange = function (event) {
91 var _a, _b, _c, _d;
92 _this.setState({ activeIndex: NONE, inputValue: event.currentTarget.value });
93 (_b = (_a = _this.props).onInputChange) === null || _b === void 0 ? void 0 : _b.call(_a, event);
94 (_d = (_c = _this.props.inputProps) === null || _c === void 0 ? void 0 : _c.onChange) === null || _d === void 0 ? void 0 : _d.call(_c, event);
95 };
96 _this.handleInputKeyDown = function (event) {
97 // HACKHACK: https://github.com/palantir/blueprint/issues/4165
98 /* eslint-disable deprecation/deprecation */
99 var _a = event.currentTarget, selectionEnd = _a.selectionEnd, value = _a.value;
100 var activeIndex = _this.state.activeIndex;
101 var activeIndexToEmit = activeIndex;
102 if (event.which === common_1.Keys.ENTER && value.length > 0) {
103 _this.addTags(value, "default");
104 }
105 else if (selectionEnd === 0 && _this.props.values.length > 0) {
106 // cursor at beginning of input allows interaction with tags.
107 // use selectionEnd to verify cursor position and no text selection.
108 if (event.which === common_1.Keys.ARROW_LEFT || event.which === common_1.Keys.ARROW_RIGHT) {
109 var nextActiveIndex = _this.getNextActiveIndex(event.which === common_1.Keys.ARROW_RIGHT ? 1 : -1);
110 if (nextActiveIndex !== activeIndex) {
111 event.stopPropagation();
112 activeIndexToEmit = nextActiveIndex;
113 _this.setState({ activeIndex: nextActiveIndex });
114 }
115 }
116 else if (event.which === common_1.Keys.BACKSPACE) {
117 _this.handleBackspaceToRemove(event);
118 }
119 else if (event.which === common_1.Keys.DELETE) {
120 _this.handleDeleteToRemove(event);
121 }
122 }
123 _this.invokeKeyPressCallback("onKeyDown", event, activeIndexToEmit);
124 };
125 _this.handleInputKeyUp = function (event) {
126 _this.invokeKeyPressCallback("onKeyUp", event, _this.state.activeIndex);
127 };
128 _this.handleInputPaste = function (event) {
129 var separator = _this.props.separator;
130 var value = event.clipboardData.getData("text");
131 if (!_this.props.addOnPaste || value.length === 0) {
132 return;
133 }
134 // special case as a UX nicety: if the user pasted only one value with no delimiters in it, leave that value in
135 // the input field so that the user can refine it before converting it to a tag manually.
136 if (separator === false || value.split(separator).length === 1) {
137 return;
138 }
139 event.preventDefault();
140 _this.addTags(value, "paste");
141 };
142 _this.handleRemoveTag = function (event) {
143 // using data attribute to simplify callback logic -- one handler for all children
144 var index = +event.currentTarget.parentElement.getAttribute("data-tag-index");
145 _this.removeIndexFromValues(index);
146 };
147 return _this;
148 }
149 TagInput.getDerivedStateFromProps = function (props, state) {
150 if (props.inputValue !== state.prevInputValueProp) {
151 return {
152 inputValue: props.inputValue,
153 prevInputValueProp: props.inputValue,
154 };
155 }
156 return null;
157 };
158 TagInput.prototype.render = function () {
159 var _a;
160 var _b = this.props, className = _b.className, disabled = _b.disabled, fill = _b.fill, inputProps = _b.inputProps, intent = _b.intent, large = _b.large, leftIcon = _b.leftIcon, placeholder = _b.placeholder, values = _b.values;
161 var classes = (0, classnames_1.default)(common_1.Classes.INPUT, common_1.Classes.TAG_INPUT, (_a = {},
162 _a[common_1.Classes.ACTIVE] = this.state.isInputFocused,
163 _a[common_1.Classes.DISABLED] = disabled,
164 _a[common_1.Classes.FILL] = fill,
165 _a[common_1.Classes.LARGE] = large,
166 _a), common_1.Classes.intentClass(intent), className);
167 var isLarge = classes.indexOf(common_1.Classes.LARGE) > NONE;
168 // use placeholder prop only if it's defined and values list is empty or contains only falsy values
169 var isSomeValueDefined = values.some(function (val) { return !!val; });
170 var resolvedPlaceholder = placeholder == null || isSomeValueDefined ? inputProps === null || inputProps === void 0 ? void 0 : inputProps.placeholder : placeholder;
171 return (React.createElement("div", { className: classes, onBlur: this.handleContainerBlur, onClick: this.handleContainerClick },
172 React.createElement(icon_1.Icon, { className: common_1.Classes.TAG_INPUT_ICON, icon: leftIcon, size: isLarge ? icon_1.IconSize.LARGE : icon_1.IconSize.STANDARD }),
173 React.createElement("div", { className: common_1.Classes.TAG_INPUT_VALUES },
174 values.map(this.maybeRenderTag),
175 this.props.children,
176 React.createElement("input", tslib_1.__assign({ value: this.state.inputValue }, inputProps, { onFocus: this.handleInputFocus, onChange: this.handleInputChange, onKeyDown: this.handleInputKeyDown, onKeyUp: this.handleInputKeyUp, onPaste: this.handleInputPaste, placeholder: resolvedPlaceholder, ref: this.handleRef, className: (0, classnames_1.default)(common_1.Classes.INPUT_GHOST, inputProps === null || inputProps === void 0 ? void 0 : inputProps.className), disabled: disabled }))),
177 this.props.rightElement));
178 };
179 TagInput.prototype.componentDidUpdate = function (prevProps) {
180 if (prevProps.inputRef !== this.props.inputRef) {
181 (0, common_1.setRef)(prevProps.inputRef, null);
182 this.handleRef = (0, common_1.refHandler)(this, "inputElement", this.props.inputRef);
183 (0, common_1.setRef)(this.props.inputRef, this.inputElement);
184 }
185 };
186 TagInput.prototype.getNextActiveIndex = function (direction) {
187 var activeIndex = this.state.activeIndex;
188 if (activeIndex === NONE) {
189 // nothing active & moving left: select last defined value. otherwise select nothing.
190 return direction < 0 ? this.findNextIndex(this.props.values.length, -1) : NONE;
191 }
192 else {
193 // otherwise, move in direction and clamp to bounds.
194 // note that upper bound allows going one beyond last item
195 // so focus can move off the right end, into the text input.
196 return this.findNextIndex(activeIndex, direction);
197 }
198 };
199 TagInput.prototype.findNextIndex = function (startIndex, direction) {
200 var values = this.props.values;
201 var index = startIndex + direction;
202 while (index > 0 && index < values.length && !values[index]) {
203 index += direction;
204 }
205 return common_1.Utils.clamp(index, 0, values.length);
206 };
207 /**
208 * Splits inputValue on separator prop,
209 * trims whitespace from each new value,
210 * and ignores empty values.
211 */
212 TagInput.prototype.getValues = function (inputValue) {
213 var separator = this.props.separator;
214 // NOTE: split() typings define two overrides for string and RegExp.
215 // this does not play well with our union prop type, so we'll just declare it as a valid type.
216 return (separator === false ? [inputValue] : inputValue.split(separator))
217 .map(function (val) { return val.trim(); })
218 .filter(function (val) { return val.length > 0; });
219 };
220 TagInput.prototype.handleBackspaceToRemove = function (event) {
221 var previousActiveIndex = this.state.activeIndex;
222 // always move leftward one item (this will focus last item if nothing is focused)
223 this.setState({ activeIndex: this.getNextActiveIndex(-1) });
224 // delete item if there was a previous valid selection (ignore first backspace to focus last item)
225 if (this.isValidIndex(previousActiveIndex)) {
226 event.stopPropagation();
227 this.removeIndexFromValues(previousActiveIndex);
228 }
229 };
230 TagInput.prototype.handleDeleteToRemove = function (event) {
231 var activeIndex = this.state.activeIndex;
232 if (this.isValidIndex(activeIndex)) {
233 event.stopPropagation();
234 this.removeIndexFromValues(activeIndex);
235 }
236 };
237 /** Remove the item at the given index by invoking `onRemove` and `onChange` accordingly. */
238 TagInput.prototype.removeIndexFromValues = function (index) {
239 var _a = this.props, onChange = _a.onChange, onRemove = _a.onRemove, values = _a.values;
240 onRemove === null || onRemove === void 0 ? void 0 : onRemove(values[index], index);
241 onChange === null || onChange === void 0 ? void 0 : onChange(values.filter(function (_, i) { return i !== index; }));
242 };
243 TagInput.prototype.invokeKeyPressCallback = function (propCallbackName, event, activeIndex) {
244 var _a, _b, _c, _d;
245 (_b = (_a = this.props)[propCallbackName]) === null || _b === void 0 ? void 0 : _b.call(_a, event, activeIndex === NONE ? undefined : activeIndex);
246 (_d = (_c = this.props.inputProps)[propCallbackName]) === null || _d === void 0 ? void 0 : _d.call(_c, event);
247 };
248 /** Returns whether the given index represents a valid item in `this.props.values`. */
249 TagInput.prototype.isValidIndex = function (index) {
250 return index !== NONE && index < this.props.values.length;
251 };
252 TagInput.displayName = "".concat(props_1.DISPLAYNAME_PREFIX, ".TagInput");
253 TagInput.defaultProps = {
254 addOnBlur: false,
255 addOnPaste: true,
256 inputProps: {},
257 separator: /[,\n\r]/,
258 tagProps: {},
259 };
260 return TagInput;
261}(common_1.AbstractPureComponent2));
262exports.TagInput = TagInput;
263//# sourceMappingURL=tagInput.js.map
\No newline at end of file