1 | "use strict";
|
2 |
|
3 | exports.__esModule = true;
|
4 | exports.default = void 0;
|
5 |
|
6 | var _classnames = _interopRequireDefault(require("classnames"));
|
7 |
|
8 | var _propTypes = _interopRequireDefault(require("prop-types"));
|
9 |
|
10 | var _react = _interopRequireWildcard(require("react"));
|
11 |
|
12 | var _uncontrollable = require("uncontrollable");
|
13 |
|
14 | var _useTimeout = _interopRequireDefault(require("@restart/hooks/useTimeout"));
|
15 |
|
16 | var _AddToListOption = _interopRequireWildcard(require("./AddToListOption"));
|
17 |
|
18 | var _DropdownListInput = _interopRequireDefault(require("./DropdownListInput"));
|
19 |
|
20 | var _Icon = require("./Icon");
|
21 |
|
22 | var _List = _interopRequireDefault(require("./List"));
|
23 |
|
24 | var _FocusListContext = require("./FocusListContext");
|
25 |
|
26 | var _Popup = _interopRequireDefault(require("./Popup"));
|
27 |
|
28 | var _Widget = _interopRequireDefault(require("./Widget"));
|
29 |
|
30 | var _WidgetPicker = _interopRequireDefault(require("./WidgetPicker"));
|
31 |
|
32 | var _messages = require("./messages");
|
33 |
|
34 | var _A11y = require("./A11y");
|
35 |
|
36 | var _Filter = require("./Filter");
|
37 |
|
38 | var CustomPropTypes = _interopRequireWildcard(require("./PropTypes"));
|
39 |
|
40 | var _canShowCreate = _interopRequireDefault(require("./canShowCreate"));
|
41 |
|
42 | var _Accessors = require("./Accessors");
|
43 |
|
44 | var _useAutoFocus = _interopRequireDefault(require("./useAutoFocus"));
|
45 |
|
46 | var _useDropdownToggle = _interopRequireDefault(require("./useDropdownToggle"));
|
47 |
|
48 | var _useFocusManager = _interopRequireDefault(require("./useFocusManager"));
|
49 |
|
50 | var _WidgetHelpers = require("./WidgetHelpers");
|
51 |
|
52 | var _PickerCaret = _interopRequireDefault(require("./PickerCaret"));
|
53 |
|
54 | const _excluded = ["id", "autoFocus", "textField", "dataKey", "value", "defaultValue", "onChange", "open", "defaultOpen", "onToggle", "searchTerm", "defaultSearchTerm", "onSearch", "filter", "allowCreate", "delay", "focusFirstItem", "className", "containerClassName", "placeholder", "busy", "disabled", "readOnly", "selectIcon", "busySpinner", "dropUp", "tabIndex", "popupTransition", "name", "autoComplete", "onSelect", "onCreate", "onKeyPress", "onKeyDown", "onClick", "inputProps", "listProps", "renderListItem", "renderListGroup", "optionComponent", "renderValue", "groupBy", "onBlur", "onFocus", "listComponent", "popupComponent", "data", "messages"];
|
55 |
|
56 | function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
57 |
|
58 | function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
59 |
|
60 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
61 |
|
62 | function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
63 |
|
64 | function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
|
65 |
|
66 | const propTypes = {
|
67 | value: _propTypes.default.any,
|
68 |
|
69 | |
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 | onChange: _propTypes.default.func,
|
80 | open: _propTypes.default.bool,
|
81 | onToggle: _propTypes.default.func,
|
82 | data: _propTypes.default.array,
|
83 | dataKey: CustomPropTypes.accessor,
|
84 | textField: CustomPropTypes.accessor,
|
85 | allowCreate: _propTypes.default.oneOf([true, false, 'onFilter']),
|
86 |
|
87 | |
88 |
|
89 |
|
90 |
|
91 | renderValue: _propTypes.default.func,
|
92 | renderListItem: _propTypes.default.func,
|
93 | listComponent: CustomPropTypes.elementType,
|
94 | optionComponent: CustomPropTypes.elementType,
|
95 | renderPopup: _propTypes.default.func,
|
96 | renderListGroup: _propTypes.default.func,
|
97 | groupBy: CustomPropTypes.accessor,
|
98 |
|
99 | |
100 |
|
101 |
|
102 |
|
103 | onSelect: _propTypes.default.func,
|
104 | onCreate: _propTypes.default.func,
|
105 |
|
106 | |
107 |
|
108 |
|
109 | onSearch: _propTypes.default.func,
|
110 | searchTerm: _propTypes.default.string,
|
111 | busy: _propTypes.default.bool,
|
112 |
|
113 |
|
114 | selectIcon: _propTypes.default.node,
|
115 |
|
116 |
|
117 | busySpinner: _propTypes.default.node,
|
118 | placeholder: _propTypes.default.string,
|
119 | dropUp: _propTypes.default.bool,
|
120 | popupTransition: CustomPropTypes.elementType,
|
121 | disabled: CustomPropTypes.disabled.acceptsArray,
|
122 | readOnly: CustomPropTypes.disabled,
|
123 |
|
124 |
|
125 | containerClassName: _propTypes.default.string,
|
126 | inputProps: _propTypes.default.object,
|
127 | listProps: _propTypes.default.object,
|
128 | messages: _propTypes.default.shape({
|
129 | open: _propTypes.default.string,
|
130 | emptyList: CustomPropTypes.message,
|
131 | emptyFilter: CustomPropTypes.message,
|
132 | createOption: CustomPropTypes.message
|
133 | })
|
134 | };
|
135 |
|
136 | function useSearchWordBuilder(delay) {
|
137 | const timeout = (0, _useTimeout.default)();
|
138 | const wordRef = (0, _react.useRef)('');
|
139 |
|
140 | function search(character, cb) {
|
141 | let word = (wordRef.current + character).toLowerCase();
|
142 | if (!character) return;
|
143 | wordRef.current = word;
|
144 | timeout.set(() => {
|
145 | wordRef.current = '';
|
146 | cb(word);
|
147 | }, delay);
|
148 | }
|
149 |
|
150 | return search;
|
151 | }
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 | const DropdownListImpl = _react.default.forwardRef(function DropdownList(_ref, outerRef) {
|
158 | let {
|
159 | id,
|
160 | autoFocus,
|
161 | textField,
|
162 | dataKey,
|
163 | value,
|
164 | defaultValue,
|
165 | onChange,
|
166 | open,
|
167 | defaultOpen = false,
|
168 | onToggle,
|
169 | searchTerm,
|
170 | defaultSearchTerm = '',
|
171 | onSearch,
|
172 | filter = true,
|
173 | allowCreate = false,
|
174 | delay = 500,
|
175 | focusFirstItem,
|
176 | className,
|
177 | containerClassName,
|
178 | placeholder,
|
179 | busy,
|
180 | disabled,
|
181 | readOnly,
|
182 | selectIcon = _Icon.caretDown,
|
183 | busySpinner,
|
184 | dropUp,
|
185 | tabIndex,
|
186 | popupTransition,
|
187 | name,
|
188 | autoComplete,
|
189 | onSelect,
|
190 | onCreate,
|
191 | onKeyPress,
|
192 | onKeyDown,
|
193 | onClick,
|
194 | inputProps,
|
195 | listProps,
|
196 | renderListItem,
|
197 | renderListGroup,
|
198 | optionComponent,
|
199 | renderValue,
|
200 | groupBy,
|
201 | onBlur,
|
202 | onFocus,
|
203 | listComponent: ListComponent = _List.default,
|
204 | popupComponent: Popup = _Popup.default,
|
205 | data: rawData = [],
|
206 | messages: userMessages
|
207 | } = _ref,
|
208 | elementProps = _objectWithoutPropertiesLoose(_ref, _excluded);
|
209 |
|
210 | const [currentValue, handleChange] = (0, _uncontrollable.useUncontrolledProp)(value, defaultValue, onChange);
|
211 | const [currentOpen, handleOpen] = (0, _uncontrollable.useUncontrolledProp)(open, defaultOpen, onToggle);
|
212 | const [currentSearch, handleSearch] = (0, _uncontrollable.useUncontrolledProp)(searchTerm, defaultSearchTerm, onSearch);
|
213 | const ref = (0, _react.useRef)(null);
|
214 | const filterRef = (0, _react.useRef)(null);
|
215 | const listRef = (0, _react.useRef)(null);
|
216 | const inputId = (0, _WidgetHelpers.useInstanceId)(id, '_input');
|
217 | const listId = (0, _WidgetHelpers.useInstanceId)(id, '_listbox');
|
218 | const activeId = (0, _WidgetHelpers.useInstanceId)(id, '_listbox_active_option');
|
219 | const accessors = (0, _Accessors.useAccessors)(textField, dataKey);
|
220 | const messages = (0, _messages.useMessagesWithDefaults)(userMessages);
|
221 | (0, _useAutoFocus.default)(!!autoFocus, ref);
|
222 | const toggle = (0, _useDropdownToggle.default)(currentOpen, handleOpen);
|
223 | const isDisabled = disabled === true;
|
224 |
|
225 | const isReadOnly = !!readOnly;
|
226 | const [focusEvents, focused] = (0, _useFocusManager.default)(ref, {
|
227 | disabled: isDisabled,
|
228 | onBlur,
|
229 | onFocus
|
230 | }, {
|
231 | didHandle(focused) {
|
232 | if (focused) {
|
233 | if (filter) focus();
|
234 | return;
|
235 | }
|
236 |
|
237 | toggle.close();
|
238 | clearSearch();
|
239 | }
|
240 |
|
241 | });
|
242 | const data = (0, _Filter.useFilteredData)(rawData, currentOpen ? filter : false, currentSearch, accessors.text);
|
243 | const selectedItem = (0, _react.useMemo)(() => data[accessors.indexOf(data, currentValue)], [data, currentValue, accessors]);
|
244 | const list = (0, _FocusListContext.useFocusList)({
|
245 | activeId,
|
246 | scope: ref,
|
247 | focusFirstItem,
|
248 | anchorItem: currentOpen ? selectedItem : undefined
|
249 | });
|
250 | const [autofilling, setAutofilling] = (0, _react.useState)(false);
|
251 | const nextSearchChar = useSearchWordBuilder(delay);
|
252 | const focusedItem = list.getFocused();
|
253 | (0, _A11y.useActiveDescendant)(ref, activeId, focusedItem && currentOpen, [focusedItem]);
|
254 | const showCreateOption = (0, _canShowCreate.default)(allowCreate, {
|
255 | searchTerm: currentSearch,
|
256 | data,
|
257 | accessors
|
258 | });
|
259 |
|
260 | const handleCreate = event => {
|
261 | (0, _WidgetHelpers.notify)(onCreate, [currentSearch]);
|
262 | clearSearch(event);
|
263 | toggle.close();
|
264 | focus();
|
265 | };
|
266 |
|
267 | const handleSelect = (dataItem, originalEvent) => {
|
268 | if (readOnly || isDisabled) return;
|
269 | if (dataItem === undefined) return;
|
270 | originalEvent == null ? void 0 : originalEvent.preventDefault();
|
271 |
|
272 | if (dataItem === _AddToListOption.CREATE_OPTION) {
|
273 | handleCreate(originalEvent);
|
274 | return;
|
275 | }
|
276 |
|
277 | (0, _WidgetHelpers.notify)(onSelect, [dataItem, {
|
278 | originalEvent
|
279 | }]);
|
280 | change(dataItem, originalEvent, true);
|
281 | toggle.close();
|
282 | focus();
|
283 | };
|
284 |
|
285 | const handleClick = e => {
|
286 | if (readOnly || isDisabled) return;
|
287 |
|
288 | e.preventDefault();
|
289 | focus();
|
290 | toggle();
|
291 | (0, _WidgetHelpers.notify)(onClick, [e]);
|
292 | };
|
293 |
|
294 | const handleKeyDown = e => {
|
295 | if (readOnly || isDisabled) return;
|
296 | let {
|
297 | key,
|
298 | altKey,
|
299 | ctrlKey,
|
300 | shiftKey
|
301 | } = e;
|
302 | (0, _WidgetHelpers.notify)(onKeyDown, [e]);
|
303 |
|
304 | let closeWithFocus = () => {
|
305 | clearSearch();
|
306 | toggle.close();
|
307 | if (currentOpen) setTimeout(focus);
|
308 | };
|
309 |
|
310 | if (e.defaultPrevented) return;
|
311 |
|
312 | if (key === 'End' && currentOpen && !shiftKey) {
|
313 | e.preventDefault();
|
314 | list.focus(list.last());
|
315 | } else if (key === 'Home' && currentOpen && !shiftKey) {
|
316 | e.preventDefault();
|
317 | list.focus(list.first());
|
318 | } else if (key === 'Escape' && (currentOpen || currentSearch)) {
|
319 | e.preventDefault();
|
320 | closeWithFocus();
|
321 | } else if (key === 'Enter' && currentOpen && ctrlKey && showCreateOption) {
|
322 | e.preventDefault();
|
323 | handleCreate(e);
|
324 | } else if ((key === 'Enter' || key === ' ' && !filter) && currentOpen) {
|
325 | e.preventDefault();
|
326 | if (list.hasFocused()) handleSelect(list.getFocused(), e);
|
327 | } else if (key === 'ArrowDown') {
|
328 | e.preventDefault();
|
329 |
|
330 | if (!currentOpen) {
|
331 | toggle.open();
|
332 | return;
|
333 | }
|
334 |
|
335 | list.focus(list.next());
|
336 | } else if (key === 'ArrowUp') {
|
337 | e.preventDefault();
|
338 | if (altKey) return closeWithFocus();
|
339 | list.focus(list.prev());
|
340 | }
|
341 | };
|
342 |
|
343 | const handleKeyPress = e => {
|
344 | if (readOnly || isDisabled) return;
|
345 | (0, _WidgetHelpers.notify)(onKeyPress, [e]);
|
346 | if (e.defaultPrevented || filter) return;
|
347 | nextSearchChar(String.fromCharCode(e.which), word => {
|
348 | if (!currentOpen) return;
|
349 |
|
350 | let isValid = item => _Filter.presets.startsWith(accessors.text(item).toLowerCase(), word.toLowerCase());
|
351 |
|
352 | const [items, focusedItem] = list.get();
|
353 | const len = items.length;
|
354 | const startIdx = items.indexOf(focusedItem) + 1;
|
355 | const offset = startIdx >= len ? 0 : startIdx;
|
356 | let idx = 0;
|
357 | let pointer = offset;
|
358 |
|
359 | while (idx < len) {
|
360 | pointer = (idx + offset) % len;
|
361 | let item = items[pointer];
|
362 | if (isValid(list.toDataItem(item))) break;
|
363 | idx++;
|
364 | }
|
365 |
|
366 | if (idx === len) return;
|
367 | list.focus(items[pointer]);
|
368 | });
|
369 | };
|
370 |
|
371 | const handleInputChange = e => {
|
372 |
|
373 | if (!currentOpen && !e.target.value.trim()) {
|
374 | e.preventDefault();
|
375 | } else {
|
376 | search(e.target.value, e, 'input');
|
377 | }
|
378 |
|
379 | toggle.open();
|
380 | };
|
381 |
|
382 | const handleAutofillChange = e => {
|
383 | let filledValue = e.target.value.toLowerCase();
|
384 | if (filledValue === '') return void change(null);
|
385 |
|
386 | for (const item of rawData) {
|
387 | if (String(accessors.value(item)).toLowerCase() === filledValue || accessors.text(item).toLowerCase() === filledValue) {
|
388 | change(item, e);
|
389 | break;
|
390 | }
|
391 | }
|
392 | };
|
393 |
|
394 | function change(nextValue, originalEvent, selected = false) {
|
395 | if (!accessors.matches(nextValue, currentValue)) {
|
396 | (0, _WidgetHelpers.notify)(handleChange, [nextValue, {
|
397 | originalEvent,
|
398 | source: selected ? 'listbox' : 'input',
|
399 | lastValue: currentValue,
|
400 | searchTerm: currentSearch
|
401 | }]);
|
402 | clearSearch(originalEvent);
|
403 | toggle.close();
|
404 | }
|
405 | }
|
406 |
|
407 | function focus() {
|
408 | if (filter) filterRef.current.focus();else ref.current.focus();
|
409 | }
|
410 |
|
411 | function clearSearch(originalEvent) {
|
412 | search('', originalEvent, 'clear');
|
413 | }
|
414 |
|
415 | function search(nextSearchTerm, originalEvent, action = 'input') {
|
416 | if (currentSearch !== nextSearchTerm) handleSearch(nextSearchTerm, {
|
417 | action,
|
418 | originalEvent,
|
419 | lastSearchTerm: currentSearch
|
420 | });
|
421 | }
|
422 | |
423 |
|
424 |
|
425 |
|
426 |
|
427 | (0, _react.useImperativeHandle)(outerRef, () => ({
|
428 | focus
|
429 | }));
|
430 | let valueItem = accessors.findOrSelf(data, currentValue);
|
431 | let shouldRenderPopup = (0, _WidgetHelpers.useFirstFocusedRender)(focused, currentOpen);
|
432 | const widgetProps = Object.assign({}, elementProps, {
|
433 | role: 'combobox',
|
434 | id: inputId,
|
435 |
|
436 | tabIndex: filter ? -1 : tabIndex || 0,
|
437 |
|
438 | 'aria-owns': listId,
|
439 | 'aria-expanded': !!currentOpen,
|
440 | 'aria-haspopup': true,
|
441 | 'aria-busy': !!busy,
|
442 | 'aria-live': currentOpen ? 'polite' : undefined,
|
443 | 'aria-autocomplete': 'list',
|
444 | 'aria-disabled': isDisabled,
|
445 | 'aria-readonly': isReadOnly
|
446 | });
|
447 | return _react.default.createElement(_FocusListContext.FocusListContext.Provider, {
|
448 | value: list.context
|
449 | }, _react.default.createElement(_Widget.default, _extends({}, widgetProps, {
|
450 | open: !!currentOpen,
|
451 | dropUp: !!dropUp,
|
452 | focused: !!focused,
|
453 | disabled: isDisabled,
|
454 | readOnly: isReadOnly,
|
455 | autofilling: autofilling
|
456 | }, focusEvents, {
|
457 | onKeyDown: handleKeyDown,
|
458 | onKeyPress: handleKeyPress,
|
459 | className: (0, _classnames.default)(className, 'rw-dropdown-list'),
|
460 | ref: ref
|
461 | }), _react.default.createElement(_WidgetPicker.default, {
|
462 | onClick: handleClick,
|
463 | tabIndex: filter ? -1 : 0,
|
464 | className: (0, _classnames.default)(containerClassName, 'rw-widget-input')
|
465 | }, _react.default.createElement(_DropdownListInput.default, _extends({}, inputProps, {
|
466 | value: valueItem,
|
467 | dataKeyAccessor: accessors.value,
|
468 | textAccessor: accessors.text,
|
469 | name: name,
|
470 | readOnly: readOnly,
|
471 | disabled: isDisabled,
|
472 | allowSearch: !!filter,
|
473 | searchTerm: currentSearch,
|
474 | ref: filterRef,
|
475 | autoComplete: autoComplete,
|
476 | onSearch: handleInputChange,
|
477 | onAutofill: setAutofilling,
|
478 | onAutofillChange: handleAutofillChange,
|
479 | placeholder: placeholder,
|
480 | renderValue: renderValue
|
481 | })), _react.default.createElement(_PickerCaret.default, {
|
482 | visible: true,
|
483 | busy: busy,
|
484 | icon: selectIcon,
|
485 | spinner: busySpinner
|
486 | })), shouldRenderPopup && _react.default.createElement(Popup, {
|
487 | dropUp: dropUp,
|
488 | open: currentOpen,
|
489 | transition: popupTransition,
|
490 | onEntered: focus,
|
491 | onEntering: () => listRef.current.scrollIntoView()
|
492 | }, _react.default.createElement(ListComponent, _extends({}, listProps, {
|
493 | id: listId,
|
494 | data: data,
|
495 | tabIndex: -1,
|
496 | disabled: disabled,
|
497 | groupBy: groupBy,
|
498 | searchTerm: currentSearch,
|
499 | accessors: accessors,
|
500 | renderItem: renderListItem,
|
501 | renderGroup: renderListGroup,
|
502 | optionComponent: optionComponent,
|
503 | value: selectedItem,
|
504 | onChange: (d, meta) => handleSelect(d, meta.originalEvent),
|
505 | "aria-live": currentOpen ? 'polite' : undefined,
|
506 | "aria-labelledby": inputId,
|
507 | "aria-hidden": !currentOpen,
|
508 | ref: listRef,
|
509 | messages: {
|
510 | emptyList: rawData.length ? messages.emptyFilter : messages.emptyList
|
511 | }
|
512 | })), showCreateOption && _react.default.createElement(_AddToListOption.default, {
|
513 | onSelect: handleCreate
|
514 | }, messages.createOption(currentValue, currentSearch || '')))));
|
515 | });
|
516 |
|
517 | DropdownListImpl.displayName = 'DropdownList';
|
518 | DropdownListImpl.propTypes = propTypes;
|
519 | var _default = DropdownListImpl;
|
520 | exports.default = _default; |
\ | No newline at end of file |