OptionList.js 13 KB

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