OptionList.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. "use strict";
  2. var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
  3. var _typeof = require("@babel/runtime/helpers/typeof");
  4. Object.defineProperty(exports, "__esModule", {
  5. value: true
  6. });
  7. exports.default = void 0;
  8. var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
  9. var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
  10. var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
  11. var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
  12. var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
  13. var _classnames = _interopRequireDefault(require("classnames"));
  14. var _KeyCode = _interopRequireDefault(require("rc-util/lib/KeyCode"));
  15. var _useMemo = _interopRequireDefault(require("rc-util/lib/hooks/useMemo"));
  16. var _omit = _interopRequireDefault(require("rc-util/lib/omit"));
  17. var _pickAttrs = _interopRequireDefault(require("rc-util/lib/pickAttrs"));
  18. var _rcVirtualList = _interopRequireDefault(require("rc-virtual-list"));
  19. var _react = _interopRequireWildcard(require("react"));
  20. var React = _react;
  21. var _SelectContext = _interopRequireDefault(require("./SelectContext"));
  22. var _TransBtn = _interopRequireDefault(require("./TransBtn"));
  23. var _useBaseProps2 = _interopRequireDefault(require("./hooks/useBaseProps"));
  24. var _platformUtil = require("./utils/platformUtil");
  25. var _valueUtil = require("./utils/valueUtil");
  26. var _excluded = ["disabled", "title", "children", "style", "className"];
  27. function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
  28. function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
  29. // export interface OptionListProps<OptionsType extends object[]> {
  30. function isTitleType(content) {
  31. return typeof content === 'string' || typeof content === 'number';
  32. }
  33. /**
  34. * Using virtual list of option display.
  35. * Will fallback to dom if use customize render.
  36. */
  37. var OptionList = function OptionList(_, ref) {
  38. var _useBaseProps = (0, _useBaseProps2.default)(),
  39. prefixCls = _useBaseProps.prefixCls,
  40. id = _useBaseProps.id,
  41. open = _useBaseProps.open,
  42. multiple = _useBaseProps.multiple,
  43. mode = _useBaseProps.mode,
  44. searchValue = _useBaseProps.searchValue,
  45. toggleOpen = _useBaseProps.toggleOpen,
  46. notFoundContent = _useBaseProps.notFoundContent,
  47. onPopupScroll = _useBaseProps.onPopupScroll;
  48. var _React$useContext = React.useContext(_SelectContext.default),
  49. maxCount = _React$useContext.maxCount,
  50. flattenOptions = _React$useContext.flattenOptions,
  51. onActiveValue = _React$useContext.onActiveValue,
  52. defaultActiveFirstOption = _React$useContext.defaultActiveFirstOption,
  53. onSelect = _React$useContext.onSelect,
  54. menuItemSelectedIcon = _React$useContext.menuItemSelectedIcon,
  55. rawValues = _React$useContext.rawValues,
  56. fieldNames = _React$useContext.fieldNames,
  57. virtual = _React$useContext.virtual,
  58. direction = _React$useContext.direction,
  59. listHeight = _React$useContext.listHeight,
  60. listItemHeight = _React$useContext.listItemHeight,
  61. optionRender = _React$useContext.optionRender;
  62. var itemPrefixCls = "".concat(prefixCls, "-item");
  63. var memoFlattenOptions = (0, _useMemo.default)(function () {
  64. return flattenOptions;
  65. }, [open, flattenOptions], function (prev, next) {
  66. return next[0] && prev[1] !== next[1];
  67. });
  68. // =========================== List ===========================
  69. var listRef = React.useRef(null);
  70. var overMaxCount = React.useMemo(function () {
  71. return multiple && (0, _valueUtil.isValidCount)(maxCount) && (rawValues === null || rawValues === void 0 ? void 0 : rawValues.size) >= maxCount;
  72. }, [multiple, maxCount, rawValues === null || rawValues === void 0 ? void 0 : rawValues.size]);
  73. var onListMouseDown = function onListMouseDown(event) {
  74. event.preventDefault();
  75. };
  76. var scrollIntoView = function scrollIntoView(args) {
  77. var _listRef$current;
  78. (_listRef$current = listRef.current) === null || _listRef$current === void 0 || _listRef$current.scrollTo(typeof args === 'number' ? {
  79. index: args
  80. } : args);
  81. };
  82. // https://github.com/ant-design/ant-design/issues/34975
  83. var isSelected = React.useCallback(function (value) {
  84. if (mode === 'combobox') {
  85. return false;
  86. }
  87. return rawValues.has(value);
  88. }, [mode, (0, _toConsumableArray2.default)(rawValues).toString(), rawValues.size]);
  89. // ========================== Active ==========================
  90. var getEnabledActiveIndex = function getEnabledActiveIndex(index) {
  91. var offset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
  92. var len = memoFlattenOptions.length;
  93. for (var i = 0; i < len; i += 1) {
  94. var current = (index + i * offset + len) % len;
  95. var _ref = memoFlattenOptions[current] || {},
  96. group = _ref.group,
  97. data = _ref.data;
  98. if (!group && !(data !== null && data !== void 0 && data.disabled) && (isSelected(data.value) || !overMaxCount)) {
  99. return current;
  100. }
  101. }
  102. return -1;
  103. };
  104. var _React$useState = React.useState(function () {
  105. return getEnabledActiveIndex(0);
  106. }),
  107. _React$useState2 = (0, _slicedToArray2.default)(_React$useState, 2),
  108. activeIndex = _React$useState2[0],
  109. setActiveIndex = _React$useState2[1];
  110. var setActive = function setActive(index) {
  111. var fromKeyboard = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  112. setActiveIndex(index);
  113. var info = {
  114. source: fromKeyboard ? 'keyboard' : 'mouse'
  115. };
  116. // Trigger active event
  117. var flattenItem = memoFlattenOptions[index];
  118. if (!flattenItem) {
  119. onActiveValue(null, -1, info);
  120. return;
  121. }
  122. onActiveValue(flattenItem.value, index, info);
  123. };
  124. // Auto active first item when list length or searchValue changed
  125. (0, _react.useEffect)(function () {
  126. setActive(defaultActiveFirstOption !== false ? getEnabledActiveIndex(0) : -1);
  127. }, [memoFlattenOptions.length, searchValue]);
  128. // https://github.com/ant-design/ant-design/issues/48036
  129. var isAriaSelected = React.useCallback(function (value) {
  130. if (mode === 'combobox') {
  131. return String(value).toLowerCase() === searchValue.toLowerCase();
  132. }
  133. return rawValues.has(value);
  134. }, [mode, searchValue, (0, _toConsumableArray2.default)(rawValues).toString(), rawValues.size]);
  135. // Auto scroll to item position in single mode
  136. (0, _react.useEffect)(function () {
  137. /**
  138. * React will skip `onChange` when component update.
  139. * `setActive` function will call root accessibility state update which makes re-render.
  140. * So we need to delay to let Input component trigger onChange first.
  141. */
  142. var timeoutId = setTimeout(function () {
  143. if (!multiple && open && rawValues.size === 1) {
  144. var value = Array.from(rawValues)[0];
  145. // Scroll to the option closest to the searchValue if searching.
  146. var index = memoFlattenOptions.findIndex(function (_ref2) {
  147. var data = _ref2.data;
  148. return searchValue ? String(data.value).startsWith(searchValue) : data.value === value;
  149. });
  150. if (index !== -1) {
  151. setActive(index);
  152. scrollIntoView(index);
  153. }
  154. }
  155. });
  156. // Force trigger scrollbar visible when open
  157. if (open) {
  158. var _listRef$current2;
  159. (_listRef$current2 = listRef.current) === null || _listRef$current2 === void 0 || _listRef$current2.scrollTo(undefined);
  160. }
  161. return function () {
  162. return clearTimeout(timeoutId);
  163. };
  164. }, [open, searchValue]);
  165. // ========================== Values ==========================
  166. var onSelectValue = function onSelectValue(value) {
  167. if (value !== undefined) {
  168. onSelect(value, {
  169. selected: !rawValues.has(value)
  170. });
  171. }
  172. // Single mode should always close by select
  173. if (!multiple) {
  174. toggleOpen(false);
  175. }
  176. };
  177. // ========================= Keyboard =========================
  178. React.useImperativeHandle(ref, function () {
  179. return {
  180. onKeyDown: function onKeyDown(event) {
  181. var which = event.which,
  182. ctrlKey = event.ctrlKey;
  183. switch (which) {
  184. // >>> Arrow keys & ctrl + n/p on Mac
  185. case _KeyCode.default.N:
  186. case _KeyCode.default.P:
  187. case _KeyCode.default.UP:
  188. case _KeyCode.default.DOWN:
  189. {
  190. var offset = 0;
  191. if (which === _KeyCode.default.UP) {
  192. offset = -1;
  193. } else if (which === _KeyCode.default.DOWN) {
  194. offset = 1;
  195. } else if ((0, _platformUtil.isPlatformMac)() && ctrlKey) {
  196. if (which === _KeyCode.default.N) {
  197. offset = 1;
  198. } else if (which === _KeyCode.default.P) {
  199. offset = -1;
  200. }
  201. }
  202. if (offset !== 0) {
  203. var nextActiveIndex = getEnabledActiveIndex(activeIndex + offset, offset);
  204. scrollIntoView(nextActiveIndex);
  205. setActive(nextActiveIndex, true);
  206. }
  207. break;
  208. }
  209. // >>> Select (Tab / Enter)
  210. case _KeyCode.default.TAB:
  211. case _KeyCode.default.ENTER:
  212. {
  213. var _item$data;
  214. // value
  215. var item = memoFlattenOptions[activeIndex];
  216. if (item && !(item !== null && item !== void 0 && (_item$data = item.data) !== null && _item$data !== void 0 && _item$data.disabled) && !overMaxCount) {
  217. onSelectValue(item.value);
  218. } else {
  219. onSelectValue(undefined);
  220. }
  221. if (open) {
  222. event.preventDefault();
  223. }
  224. break;
  225. }
  226. // >>> Close
  227. case _KeyCode.default.ESC:
  228. {
  229. toggleOpen(false);
  230. if (open) {
  231. event.stopPropagation();
  232. }
  233. }
  234. }
  235. },
  236. onKeyUp: function onKeyUp() {},
  237. scrollTo: function scrollTo(index) {
  238. scrollIntoView(index);
  239. }
  240. };
  241. });
  242. // ========================== Render ==========================
  243. if (memoFlattenOptions.length === 0) {
  244. return /*#__PURE__*/React.createElement("div", {
  245. role: "listbox",
  246. id: "".concat(id, "_list"),
  247. className: "".concat(itemPrefixCls, "-empty"),
  248. onMouseDown: onListMouseDown
  249. }, notFoundContent);
  250. }
  251. var omitFieldNameList = Object.keys(fieldNames).map(function (key) {
  252. return fieldNames[key];
  253. });
  254. var getLabel = function getLabel(item) {
  255. return item.label;
  256. };
  257. function getItemAriaProps(item, index) {
  258. var group = item.group;
  259. return {
  260. role: group ? 'presentation' : 'option',
  261. id: "".concat(id, "_list_").concat(index)
  262. };
  263. }
  264. var renderItem = function renderItem(index) {
  265. var item = memoFlattenOptions[index];
  266. if (!item) {
  267. return null;
  268. }
  269. var itemData = item.data || {};
  270. var value = itemData.value;
  271. var group = item.group;
  272. var attrs = (0, _pickAttrs.default)(itemData, true);
  273. var mergedLabel = getLabel(item);
  274. return item ? /*#__PURE__*/React.createElement("div", (0, _extends2.default)({
  275. "aria-label": typeof mergedLabel === 'string' && !group ? mergedLabel : null
  276. }, attrs, {
  277. key: index
  278. }, getItemAriaProps(item, index), {
  279. "aria-selected": isAriaSelected(value)
  280. }), value) : null;
  281. };
  282. var a11yProps = {
  283. role: 'listbox',
  284. id: "".concat(id, "_list")
  285. };
  286. return /*#__PURE__*/React.createElement(React.Fragment, null, virtual && /*#__PURE__*/React.createElement("div", (0, _extends2.default)({}, a11yProps, {
  287. style: {
  288. height: 0,
  289. width: 0,
  290. overflow: 'hidden'
  291. }
  292. }), renderItem(activeIndex - 1), renderItem(activeIndex), renderItem(activeIndex + 1)), /*#__PURE__*/React.createElement(_rcVirtualList.default, {
  293. itemKey: "key",
  294. ref: listRef,
  295. data: memoFlattenOptions,
  296. height: listHeight,
  297. itemHeight: listItemHeight,
  298. fullHeight: false,
  299. onMouseDown: onListMouseDown,
  300. onScroll: onPopupScroll,
  301. virtual: virtual,
  302. direction: direction,
  303. innerProps: virtual ? null : a11yProps
  304. }, function (item, itemIndex) {
  305. var group = item.group,
  306. groupOption = item.groupOption,
  307. data = item.data,
  308. label = item.label,
  309. value = item.value;
  310. var key = data.key;
  311. // Group
  312. if (group) {
  313. var _data$title;
  314. var groupTitle = (_data$title = data.title) !== null && _data$title !== void 0 ? _data$title : isTitleType(label) ? label.toString() : undefined;
  315. return /*#__PURE__*/React.createElement("div", {
  316. className: (0, _classnames.default)(itemPrefixCls, "".concat(itemPrefixCls, "-group"), data.className),
  317. title: groupTitle
  318. }, label !== undefined ? label : key);
  319. }
  320. var disabled = data.disabled,
  321. title = data.title,
  322. children = data.children,
  323. style = data.style,
  324. className = data.className,
  325. otherProps = (0, _objectWithoutProperties2.default)(data, _excluded);
  326. var passedProps = (0, _omit.default)(otherProps, omitFieldNameList);
  327. // Option
  328. var selected = isSelected(value);
  329. var mergedDisabled = disabled || !selected && overMaxCount;
  330. var optionPrefixCls = "".concat(itemPrefixCls, "-option");
  331. var optionClassName = (0, _classnames.default)(itemPrefixCls, optionPrefixCls, className, (0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)((0, _defineProperty2.default)({}, "".concat(optionPrefixCls, "-grouped"), groupOption), "".concat(optionPrefixCls, "-active"), activeIndex === itemIndex && !mergedDisabled), "".concat(optionPrefixCls, "-disabled"), mergedDisabled), "".concat(optionPrefixCls, "-selected"), selected));
  332. var mergedLabel = getLabel(item);
  333. var iconVisible = !menuItemSelectedIcon || typeof menuItemSelectedIcon === 'function' || selected;
  334. // https://github.com/ant-design/ant-design/issues/34145
  335. var content = typeof mergedLabel === 'number' ? mergedLabel : mergedLabel || value;
  336. // https://github.com/ant-design/ant-design/issues/26717
  337. var optionTitle = isTitleType(content) ? content.toString() : undefined;
  338. if (title !== undefined) {
  339. optionTitle = title;
  340. }
  341. return /*#__PURE__*/React.createElement("div", (0, _extends2.default)({}, (0, _pickAttrs.default)(passedProps), !virtual ? getItemAriaProps(item, itemIndex) : {}, {
  342. "aria-selected": isAriaSelected(value),
  343. className: optionClassName,
  344. title: optionTitle,
  345. onMouseMove: function onMouseMove() {
  346. if (activeIndex === itemIndex || mergedDisabled) {
  347. return;
  348. }
  349. setActive(itemIndex);
  350. },
  351. onClick: function onClick() {
  352. if (!mergedDisabled) {
  353. onSelectValue(value);
  354. }
  355. },
  356. style: style
  357. }), /*#__PURE__*/React.createElement("div", {
  358. className: "".concat(optionPrefixCls, "-content")
  359. }, typeof optionRender === 'function' ? optionRender(item, {
  360. index: itemIndex
  361. }) : content), /*#__PURE__*/React.isValidElement(menuItemSelectedIcon) || selected, iconVisible && /*#__PURE__*/React.createElement(_TransBtn.default, {
  362. className: "".concat(itemPrefixCls, "-option-state"),
  363. customizeIcon: menuItemSelectedIcon,
  364. customizeIconProps: {
  365. value: value,
  366. disabled: mergedDisabled,
  367. isSelected: selected
  368. }
  369. }, selected ? '✓' : null));
  370. }));
  371. };
  372. var RefOptionList = /*#__PURE__*/React.forwardRef(OptionList);
  373. if (process.env.NODE_ENV !== 'production') {
  374. RefOptionList.displayName = 'OptionList';
  375. }
  376. var _default = exports.default = RefOptionList;