UNPKG

9.95 kBJavaScriptView Raw
1import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
2import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
3import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
4import * as React from 'react';
5import KeyCode from "rc-util/es/KeyCode";
6import pickAttrs from "rc-util/es/pickAttrs";
7import useMemo from "rc-util/es/hooks/useMemo";
8import classNames from 'classnames';
9import List from 'rc-virtual-list';
10import TransBtn from './TransBtn';
11/**
12 * Using virtual list of option display.
13 * Will fallback to dom if use customize render.
14 */
15
16var OptionList = function OptionList(_ref, ref) {
17 var prefixCls = _ref.prefixCls,
18 id = _ref.id,
19 flattenOptions = _ref.flattenOptions,
20 childrenAsData = _ref.childrenAsData,
21 values = _ref.values,
22 searchValue = _ref.searchValue,
23 multiple = _ref.multiple,
24 defaultActiveFirstOption = _ref.defaultActiveFirstOption,
25 height = _ref.height,
26 itemHeight = _ref.itemHeight,
27 notFoundContent = _ref.notFoundContent,
28 open = _ref.open,
29 menuItemSelectedIcon = _ref.menuItemSelectedIcon,
30 virtual = _ref.virtual,
31 onSelect = _ref.onSelect,
32 onToggleOpen = _ref.onToggleOpen,
33 onActiveValue = _ref.onActiveValue,
34 onScroll = _ref.onScroll,
35 onMouseEnter = _ref.onMouseEnter;
36 var itemPrefixCls = "".concat(prefixCls, "-item");
37 var memoFlattenOptions = useMemo(function () {
38 return flattenOptions;
39 }, [open, flattenOptions], function (prev, next) {
40 return next[0] && prev[1] !== next[1];
41 }); // =========================== List ===========================
42
43 var listRef = React.useRef(null);
44
45 var onListMouseDown = function onListMouseDown(event) {
46 event.preventDefault();
47 };
48
49 var scrollIntoView = function scrollIntoView(index) {
50 if (listRef.current) {
51 listRef.current.scrollTo({
52 index: index
53 });
54 }
55 }; // ========================== Active ==========================
56
57
58 var getEnabledActiveIndex = function getEnabledActiveIndex(index) {
59 var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
60 var len = memoFlattenOptions.length;
61
62 for (var i = 0; i < len; i += 1) {
63 var current = (index + i * offset + len) % len;
64 var _memoFlattenOptions$c = memoFlattenOptions[current],
65 group = _memoFlattenOptions$c.group,
66 data = _memoFlattenOptions$c.data;
67
68 if (!group && !data.disabled) {
69 return current;
70 }
71 }
72
73 return -1;
74 };
75
76 var _React$useState = React.useState(function () {
77 return getEnabledActiveIndex(0);
78 }),
79 _React$useState2 = _slicedToArray(_React$useState, 2),
80 activeIndex = _React$useState2[0],
81 setActiveIndex = _React$useState2[1];
82
83 var setActive = function setActive(index) {
84 setActiveIndex(index); // Trigger active event
85
86 var flattenItem = memoFlattenOptions[index];
87
88 if (!flattenItem) {
89 onActiveValue(null, -1);
90 return;
91 }
92
93 onActiveValue(flattenItem.data.value, index);
94 }; // Auto active first item when list length or searchValue changed
95
96
97 React.useEffect(function () {
98 setActive(defaultActiveFirstOption !== false ? getEnabledActiveIndex(0) : -1);
99 }, [memoFlattenOptions.length, searchValue]); // Auto scroll to item position in single mode
100
101 React.useEffect(function () {
102 /**
103 * React will skip `onChange` when component update.
104 * `setActive` function will call root accessibility state update which makes re-render.
105 * So we need to delay to let Input component trigger onChange first.
106 */
107 var timeoutId = setTimeout(function () {
108 if (!multiple && open && values.size === 1) {
109 var value = Array.from(values)[0];
110 var index = memoFlattenOptions.findIndex(function (_ref2) {
111 var data = _ref2.data;
112 return data.value === value;
113 });
114 setActive(index);
115 scrollIntoView(index);
116 }
117 });
118 return function () {
119 return clearTimeout(timeoutId);
120 };
121 }, [open]); // ========================== Values ==========================
122
123 var onSelectValue = function onSelectValue(value) {
124 if (value !== undefined) {
125 onSelect(value, {
126 selected: !values.has(value)
127 });
128 } // Single mode should always close by select
129
130
131 if (!multiple) {
132 onToggleOpen(false);
133 }
134 }; // ========================= Keyboard =========================
135
136
137 React.useImperativeHandle(ref, function () {
138 return {
139 onKeyDown: function onKeyDown(event) {
140 var which = event.which;
141
142 switch (which) {
143 // >>> Arrow keys
144 case KeyCode.UP:
145 case KeyCode.DOWN:
146 {
147 var offset = 0;
148
149 if (which === KeyCode.UP) {
150 offset = -1;
151 } else if (which === KeyCode.DOWN) {
152 offset = 1;
153 }
154
155 if (offset !== 0) {
156 var nextActiveIndex = getEnabledActiveIndex(activeIndex + offset, offset);
157 scrollIntoView(nextActiveIndex);
158 setActive(nextActiveIndex);
159 }
160
161 break;
162 }
163 // >>> Select
164
165 case KeyCode.ENTER:
166 {
167 // value
168 var item = memoFlattenOptions[activeIndex];
169
170 if (item && !item.data.disabled) {
171 onSelectValue(item.data.value);
172 } else {
173 onSelectValue(undefined);
174 }
175
176 if (open) {
177 event.preventDefault();
178 }
179
180 break;
181 }
182 // >>> Close
183
184 case KeyCode.ESC:
185 {
186 onToggleOpen(false);
187 }
188 }
189 },
190 onKeyUp: function onKeyUp() {},
191 scrollTo: function scrollTo(index) {
192 scrollIntoView(index);
193 }
194 };
195 }); // ========================== Render ==========================
196
197 if (memoFlattenOptions.length === 0) {
198 return React.createElement("div", {
199 role: "listbox",
200 id: "".concat(id, "_list"),
201 className: "".concat(itemPrefixCls, "-empty"),
202 onMouseDown: onListMouseDown
203 }, notFoundContent);
204 }
205
206 function renderItem(index) {
207 var item = memoFlattenOptions[index];
208 if (!item) return null;
209 var itemData = item.data || {};
210 var value = itemData.value,
211 label = itemData.label,
212 children = itemData.children;
213 var attrs = pickAttrs(itemData, true);
214 var mergedLabel = childrenAsData ? children : label;
215 return item ? React.createElement("div", Object.assign({
216 "aria-label": typeof mergedLabel === 'string' ? mergedLabel : null
217 }, attrs, {
218 key: index,
219 role: "option",
220 id: "".concat(id, "_list_").concat(index),
221 "aria-selected": values.has(value)
222 }), value) : null;
223 }
224
225 return React.createElement(React.Fragment, null, React.createElement("div", {
226 role: "listbox",
227 id: "".concat(id, "_list"),
228 style: {
229 height: 0,
230 width: 0,
231 overflow: 'hidden'
232 }
233 }, renderItem(activeIndex - 1), renderItem(activeIndex), renderItem(activeIndex + 1)), React.createElement(List, {
234 itemKey: "key",
235 ref: listRef,
236 data: memoFlattenOptions,
237 height: height,
238 itemHeight: itemHeight,
239 fullHeight: false,
240 onMouseDown: onListMouseDown,
241 onScroll: onScroll,
242 virtual: virtual,
243 onMouseEnter: onMouseEnter
244 }, function (_ref3, itemIndex) {
245 var _classNames;
246
247 var group = _ref3.group,
248 groupOption = _ref3.groupOption,
249 data = _ref3.data;
250 var label = data.label,
251 key = data.key; // Group
252
253 if (group) {
254 return React.createElement("div", {
255 className: classNames(itemPrefixCls, "".concat(itemPrefixCls, "-group"))
256 }, label !== undefined ? label : key);
257 }
258
259 var disabled = data.disabled,
260 value = data.value,
261 title = data.title,
262 children = data.children,
263 style = data.style,
264 className = data.className,
265 otherProps = _objectWithoutProperties(data, ["disabled", "value", "title", "children", "style", "className"]); // Option
266
267
268 var selected = values.has(value);
269 var optionPrefixCls = "".concat(itemPrefixCls, "-option");
270 var optionClassName = classNames(itemPrefixCls, optionPrefixCls, className, (_classNames = {}, _defineProperty(_classNames, "".concat(optionPrefixCls, "-grouped"), groupOption), _defineProperty(_classNames, "".concat(optionPrefixCls, "-active"), activeIndex === itemIndex && !disabled), _defineProperty(_classNames, "".concat(optionPrefixCls, "-disabled"), disabled), _defineProperty(_classNames, "".concat(optionPrefixCls, "-selected"), selected), _classNames));
271 var mergedLabel = childrenAsData ? children : label;
272 var iconVisible = !menuItemSelectedIcon || typeof menuItemSelectedIcon === 'function' || selected;
273 return React.createElement("div", Object.assign({}, otherProps, {
274 "aria-selected": selected,
275 className: optionClassName,
276 title: title,
277 onMouseMove: function onMouseMove() {
278 if (activeIndex === itemIndex || disabled) {
279 return;
280 }
281
282 setActive(itemIndex);
283 },
284 onClick: function onClick() {
285 if (!disabled) {
286 onSelectValue(value);
287 }
288 },
289 style: style
290 }), React.createElement("div", {
291 className: "".concat(optionPrefixCls, "-content")
292 }, mergedLabel || value), React.isValidElement(menuItemSelectedIcon) || selected, iconVisible && React.createElement(TransBtn, {
293 className: "".concat(itemPrefixCls, "-option-state"),
294 customizeIcon: menuItemSelectedIcon,
295 customizeIconProps: {
296 isSelected: selected
297 }
298 }, selected ? '✓' : null));
299 }));
300};
301
302var RefOptionList = React.forwardRef(OptionList);
303RefOptionList.displayName = 'OptionList';
304export default RefOptionList;
\No newline at end of file