Cascader.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
  2. import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
  3. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  4. import { createVNode as _createVNode, resolveDirective as _resolveDirective } from "vue";
  5. import { computed, defineComponent, ref, toRef, toRefs, watchEffect } from 'vue';
  6. import { baseSelectPropsWithoutPrivate } from '../vc-select/BaseSelect';
  7. import omit from '../_util/omit';
  8. import PropTypes from '../_util/vue-types';
  9. import { initDefaultProps } from '../_util/props-util';
  10. import useId from '../vc-select/hooks/useId';
  11. import useMergedState from '../_util/hooks/useMergedState';
  12. import { fillFieldNames, toPathKey, toPathKeys, SHOW_PARENT, SHOW_CHILD } from './utils/commonUtil';
  13. import useEntities from './hooks/useEntities';
  14. import useSearchConfig from './hooks/useSearchConfig';
  15. import useSearchOptions from './hooks/useSearchOptions';
  16. import useMissingValues from './hooks/useMissingValues';
  17. import { formatStrategyValues, toPathOptions } from './utils/treeUtil';
  18. import { conductCheck } from '../vc-tree/utils/conductUtil';
  19. import useDisplayValues from './hooks/useDisplayValues';
  20. import { useProvideCascader } from './context';
  21. import OptionList from './OptionList';
  22. import { BaseSelect } from '../vc-select';
  23. import devWarning from '../vc-util/devWarning';
  24. import useMaxLevel from '../vc-tree/useMaxLevel';
  25. export { SHOW_PARENT, SHOW_CHILD };
  26. function baseCascaderProps() {
  27. return _objectSpread(_objectSpread({}, omit(baseSelectPropsWithoutPrivate(), ['tokenSeparators', 'mode', 'showSearch'])), {}, {
  28. // MISC
  29. id: String,
  30. prefixCls: String,
  31. fieldNames: Object,
  32. children: Array,
  33. // Value
  34. value: {
  35. type: [String, Number, Array]
  36. },
  37. defaultValue: {
  38. type: [String, Number, Array]
  39. },
  40. changeOnSelect: {
  41. type: Boolean,
  42. default: undefined
  43. },
  44. displayRender: Function,
  45. checkable: {
  46. type: Boolean,
  47. default: undefined
  48. },
  49. showCheckedStrategy: {
  50. type: String,
  51. default: SHOW_PARENT
  52. },
  53. // Search
  54. showSearch: {
  55. type: [Boolean, Object],
  56. default: undefined
  57. },
  58. searchValue: String,
  59. onSearch: Function,
  60. // Trigger
  61. expandTrigger: String,
  62. // Options
  63. options: Array,
  64. /** @private Internal usage. Do not use in your production. */
  65. dropdownPrefixCls: String,
  66. loadData: Function,
  67. // Open
  68. /** @deprecated Use `open` instead */
  69. popupVisible: {
  70. type: Boolean,
  71. default: undefined
  72. },
  73. /** @deprecated Use `dropdownClassName` instead */
  74. popupClassName: String,
  75. dropdownClassName: String,
  76. dropdownMenuColumnStyle: {
  77. type: Object,
  78. default: undefined
  79. },
  80. /** @deprecated Use `dropdownStyle` instead */
  81. popupStyle: {
  82. type: Object,
  83. default: undefined
  84. },
  85. dropdownStyle: {
  86. type: Object,
  87. default: undefined
  88. },
  89. /** @deprecated Use `placement` instead */
  90. popupPlacement: String,
  91. placement: String,
  92. /** @deprecated Use `onDropdownVisibleChange` instead */
  93. onPopupVisibleChange: Function,
  94. onDropdownVisibleChange: Function,
  95. // Icon
  96. expandIcon: PropTypes.any,
  97. loadingIcon: PropTypes.any
  98. });
  99. }
  100. export function singleCascaderProps() {
  101. return _objectSpread(_objectSpread({}, baseCascaderProps()), {}, {
  102. checkable: Boolean,
  103. onChange: Function
  104. });
  105. }
  106. export function multipleCascaderProps() {
  107. return _objectSpread(_objectSpread({}, baseCascaderProps()), {}, {
  108. checkable: Boolean,
  109. onChange: Function
  110. });
  111. }
  112. export function internalCascaderProps() {
  113. return _objectSpread(_objectSpread({}, baseCascaderProps()), {}, {
  114. onChange: Function,
  115. customSlots: Object
  116. });
  117. }
  118. function isMultipleValue(value) {
  119. return Array.isArray(value) && Array.isArray(value[0]);
  120. }
  121. function toRawValues(value) {
  122. if (!value) {
  123. return [];
  124. }
  125. if (isMultipleValue(value)) {
  126. return value;
  127. }
  128. return (value.length === 0 ? [] : [value]).map(function (val) {
  129. return Array.isArray(val) ? val : [val];
  130. });
  131. }
  132. export default defineComponent({
  133. compatConfig: {
  134. MODE: 3
  135. },
  136. name: 'Cascader',
  137. inheritAttrs: false,
  138. props: initDefaultProps(internalCascaderProps(), {}),
  139. setup: function setup(props, _ref) {
  140. var attrs = _ref.attrs,
  141. expose = _ref.expose,
  142. slots = _ref.slots;
  143. var mergedId = useId(toRef(props, 'id'));
  144. var multiple = computed(function () {
  145. return !!props.checkable;
  146. });
  147. // =========================== Values ===========================
  148. var _useMergedState = useMergedState(props.defaultValue, {
  149. value: computed(function () {
  150. return props.value;
  151. }),
  152. postState: toRawValues
  153. }),
  154. _useMergedState2 = _slicedToArray(_useMergedState, 2),
  155. rawValues = _useMergedState2[0],
  156. setRawValues = _useMergedState2[1];
  157. // ========================= FieldNames =========================
  158. var mergedFieldNames = computed(function () {
  159. return fillFieldNames(props.fieldNames);
  160. });
  161. // =========================== Option ===========================
  162. var mergedOptions = computed(function () {
  163. return props.options || [];
  164. });
  165. // Only used in multiple mode, this fn will not call in single mode
  166. var pathKeyEntities = useEntities(mergedOptions, mergedFieldNames);
  167. /** Convert path key back to value format */
  168. var getValueByKeyPath = function getValueByKeyPath(pathKeys) {
  169. var keyPathEntities = pathKeyEntities.value;
  170. return pathKeys.map(function (pathKey) {
  171. var nodes = keyPathEntities[pathKey].nodes;
  172. return nodes.map(function (node) {
  173. return node[mergedFieldNames.value.value];
  174. });
  175. });
  176. };
  177. // =========================== Search ===========================
  178. var _useMergedState3 = useMergedState('', {
  179. value: computed(function () {
  180. return props.searchValue;
  181. }),
  182. postState: function postState(search) {
  183. return search || '';
  184. }
  185. }),
  186. _useMergedState4 = _slicedToArray(_useMergedState3, 2),
  187. mergedSearchValue = _useMergedState4[0],
  188. setSearchValue = _useMergedState4[1];
  189. var onInternalSearch = function onInternalSearch(searchText, info) {
  190. setSearchValue(searchText);
  191. if (info.source !== 'blur' && props.onSearch) {
  192. props.onSearch(searchText);
  193. }
  194. };
  195. var _useSearchConfig = useSearchConfig(toRef(props, 'showSearch')),
  196. mergedShowSearch = _useSearchConfig.showSearch,
  197. mergedSearchConfig = _useSearchConfig.searchConfig;
  198. var searchOptions = useSearchOptions(mergedSearchValue, mergedOptions, mergedFieldNames, computed(function () {
  199. return props.dropdownPrefixCls || props.prefixCls;
  200. }), mergedSearchConfig, toRef(props, 'changeOnSelect'));
  201. // =========================== Values ===========================
  202. var missingValuesInfo = useMissingValues(mergedOptions, mergedFieldNames, rawValues);
  203. // Fill `rawValues` with checked conduction values
  204. var _ref2 = [ref([]), ref([]), ref([])],
  205. checkedValues = _ref2[0],
  206. halfCheckedValues = _ref2[1],
  207. missingCheckedValues = _ref2[2];
  208. var _useMaxLevel = useMaxLevel(pathKeyEntities),
  209. maxLevel = _useMaxLevel.maxLevel,
  210. levelEntities = _useMaxLevel.levelEntities;
  211. watchEffect(function () {
  212. var _missingValuesInfo$va = _slicedToArray(missingValuesInfo.value, 2),
  213. existValues = _missingValuesInfo$va[0],
  214. missingValues = _missingValuesInfo$va[1];
  215. if (!multiple.value || !rawValues.value.length) {
  216. var _ref3 = [existValues, [], missingValues];
  217. checkedValues.value = _ref3[0];
  218. halfCheckedValues.value = _ref3[1];
  219. missingCheckedValues.value = _ref3[2];
  220. return;
  221. }
  222. var keyPathValues = toPathKeys(existValues);
  223. var keyPathEntities = pathKeyEntities.value;
  224. var _conductCheck = conductCheck(keyPathValues, true, keyPathEntities, maxLevel.value, levelEntities.value),
  225. checkedKeys = _conductCheck.checkedKeys,
  226. halfCheckedKeys = _conductCheck.halfCheckedKeys;
  227. // Convert key back to value cells
  228. var _ref4 = [getValueByKeyPath(checkedKeys), getValueByKeyPath(halfCheckedKeys), missingValues];
  229. checkedValues.value = _ref4[0];
  230. halfCheckedValues.value = _ref4[1];
  231. missingCheckedValues.value = _ref4[2];
  232. });
  233. var deDuplicatedValues = computed(function () {
  234. var checkedKeys = toPathKeys(checkedValues.value);
  235. var deduplicateKeys = formatStrategyValues(checkedKeys, pathKeyEntities.value, props.showCheckedStrategy);
  236. return [].concat(_toConsumableArray(missingCheckedValues.value), _toConsumableArray(getValueByKeyPath(deduplicateKeys)));
  237. });
  238. var displayValues = useDisplayValues(deDuplicatedValues, mergedOptions, mergedFieldNames, multiple, toRef(props, 'displayRender'));
  239. // =========================== Change ===========================
  240. var triggerChange = function triggerChange(nextValues) {
  241. setRawValues(nextValues);
  242. // Save perf if no need trigger event
  243. if (props.onChange) {
  244. var nextRawValues = toRawValues(nextValues);
  245. var valueOptions = nextRawValues.map(function (valueCells) {
  246. return toPathOptions(valueCells, mergedOptions.value, mergedFieldNames.value).map(function (valueOpt) {
  247. return valueOpt.option;
  248. });
  249. });
  250. var triggerValues = multiple.value ? nextRawValues : nextRawValues[0];
  251. var triggerOptions = multiple.value ? valueOptions : valueOptions[0];
  252. props.onChange(triggerValues, triggerOptions);
  253. }
  254. };
  255. // =========================== Select ===========================
  256. var onInternalSelect = function onInternalSelect(valuePath) {
  257. setSearchValue('');
  258. if (!multiple.value) {
  259. triggerChange(valuePath);
  260. } else {
  261. // Prepare conduct required info
  262. var pathKey = toPathKey(valuePath);
  263. var checkedPathKeys = toPathKeys(checkedValues.value);
  264. var halfCheckedPathKeys = toPathKeys(halfCheckedValues.value);
  265. var existInChecked = checkedPathKeys.includes(pathKey);
  266. var existInMissing = missingCheckedValues.value.some(function (valueCells) {
  267. return toPathKey(valueCells) === pathKey;
  268. });
  269. // Do update
  270. var nextCheckedValues = checkedValues.value;
  271. var nextMissingValues = missingCheckedValues.value;
  272. if (existInMissing && !existInChecked) {
  273. // Missing value only do filter
  274. nextMissingValues = missingCheckedValues.value.filter(function (valueCells) {
  275. return toPathKey(valueCells) !== pathKey;
  276. });
  277. } else {
  278. // Update checked key first
  279. var nextRawCheckedKeys = existInChecked ? checkedPathKeys.filter(function (key) {
  280. return key !== pathKey;
  281. }) : [].concat(_toConsumableArray(checkedPathKeys), [pathKey]);
  282. // Conduction by selected or not
  283. var checkedKeys;
  284. if (existInChecked) {
  285. var _conductCheck2 = conductCheck(nextRawCheckedKeys, {
  286. checked: false,
  287. halfCheckedKeys: halfCheckedPathKeys
  288. }, pathKeyEntities.value, maxLevel.value, levelEntities.value);
  289. checkedKeys = _conductCheck2.checkedKeys;
  290. } else {
  291. var _conductCheck3 = conductCheck(nextRawCheckedKeys, true, pathKeyEntities.value, maxLevel.value, levelEntities.value);
  292. checkedKeys = _conductCheck3.checkedKeys;
  293. }
  294. // Roll up to parent level keys
  295. var deDuplicatedKeys = formatStrategyValues(checkedKeys, pathKeyEntities.value, props.showCheckedStrategy);
  296. nextCheckedValues = getValueByKeyPath(deDuplicatedKeys);
  297. }
  298. triggerChange([].concat(_toConsumableArray(nextMissingValues), _toConsumableArray(nextCheckedValues)));
  299. }
  300. };
  301. // Display Value change logic
  302. var onDisplayValuesChange = function onDisplayValuesChange(_, info) {
  303. if (info.type === 'clear') {
  304. triggerChange([]);
  305. return;
  306. }
  307. // Cascader do not support `add` type. Only support `remove`
  308. var valueCells = info.values[0].valueCells;
  309. onInternalSelect(valueCells);
  310. };
  311. // ============================ Open ============================
  312. if (process.env.NODE_ENV !== 'production') {
  313. watchEffect(function () {
  314. devWarning(!props.onPopupVisibleChange, 'Cascader', '`popupVisibleChange` is deprecated. Please use `dropdownVisibleChange` instead.');
  315. devWarning(props.popupVisible === undefined, 'Cascader', '`popupVisible` is deprecated. Please use `open` instead.');
  316. devWarning(props.popupClassName === undefined, 'Cascader', '`popupClassName` is deprecated. Please use `dropdownClassName` instead.');
  317. devWarning(props.popupPlacement === undefined, 'Cascader', '`popupPlacement` is deprecated. Please use `placement` instead.');
  318. devWarning(props.popupStyle === undefined, 'Cascader', '`popupStyle` is deprecated. Please use `dropdownStyle` instead.');
  319. });
  320. }
  321. var mergedOpen = computed(function () {
  322. return props.open !== undefined ? props.open : props.popupVisible;
  323. });
  324. var mergedDropdownClassName = computed(function () {
  325. return props.dropdownClassName || props.popupClassName;
  326. });
  327. var mergedDropdownStyle = computed(function () {
  328. return props.dropdownStyle || props.popupStyle || {};
  329. });
  330. var mergedPlacement = computed(function () {
  331. return props.placement || props.popupPlacement;
  332. });
  333. var onInternalDropdownVisibleChange = function onInternalDropdownVisibleChange(nextVisible) {
  334. var _props$onDropdownVisi, _props$onPopupVisible;
  335. (_props$onDropdownVisi = props.onDropdownVisibleChange) === null || _props$onDropdownVisi === void 0 ? void 0 : _props$onDropdownVisi.call(props, nextVisible);
  336. (_props$onPopupVisible = props.onPopupVisibleChange) === null || _props$onPopupVisible === void 0 ? void 0 : _props$onPopupVisible.call(props, nextVisible);
  337. };
  338. var _toRefs = toRefs(props),
  339. changeOnSelect = _toRefs.changeOnSelect,
  340. checkable = _toRefs.checkable,
  341. dropdownPrefixCls = _toRefs.dropdownPrefixCls,
  342. loadData = _toRefs.loadData,
  343. expandTrigger = _toRefs.expandTrigger,
  344. expandIcon = _toRefs.expandIcon,
  345. loadingIcon = _toRefs.loadingIcon,
  346. dropdownMenuColumnStyle = _toRefs.dropdownMenuColumnStyle,
  347. customSlots = _toRefs.customSlots;
  348. useProvideCascader({
  349. options: mergedOptions,
  350. fieldNames: mergedFieldNames,
  351. values: checkedValues,
  352. halfValues: halfCheckedValues,
  353. changeOnSelect: changeOnSelect,
  354. onSelect: onInternalSelect,
  355. checkable: checkable,
  356. searchOptions: searchOptions,
  357. dropdownPrefixCls: dropdownPrefixCls,
  358. loadData: loadData,
  359. expandTrigger: expandTrigger,
  360. expandIcon: expandIcon,
  361. loadingIcon: loadingIcon,
  362. dropdownMenuColumnStyle: dropdownMenuColumnStyle,
  363. customSlots: customSlots
  364. });
  365. var selectRef = ref();
  366. expose({
  367. focus: function focus() {
  368. var _selectRef$value;
  369. (_selectRef$value = selectRef.value) === null || _selectRef$value === void 0 ? void 0 : _selectRef$value.focus();
  370. },
  371. blur: function blur() {
  372. var _selectRef$value2;
  373. (_selectRef$value2 = selectRef.value) === null || _selectRef$value2 === void 0 ? void 0 : _selectRef$value2.blur();
  374. },
  375. scrollTo: function scrollTo(arg) {
  376. var _selectRef$value3;
  377. (_selectRef$value3 = selectRef.value) === null || _selectRef$value3 === void 0 ? void 0 : _selectRef$value3.scrollTo(arg);
  378. }
  379. });
  380. var pickProps = computed(function () {
  381. return omit(props, ['id', 'prefixCls', 'fieldNames',
  382. // Value
  383. 'defaultValue', 'value', 'changeOnSelect', 'onChange', 'displayRender', 'checkable',
  384. // Search
  385. 'searchValue', 'onSearch', 'showSearch',
  386. // Trigger
  387. 'expandTrigger',
  388. // Options
  389. 'options', 'dropdownPrefixCls', 'loadData',
  390. // Open
  391. 'popupVisible', 'open', 'popupClassName', 'dropdownClassName', 'dropdownMenuColumnStyle', 'popupPlacement', 'placement', 'onDropdownVisibleChange', 'onPopupVisibleChange',
  392. // Icon
  393. 'expandIcon', 'loadingIcon', 'customSlots', 'showCheckedStrategy',
  394. // Children
  395. 'children']);
  396. });
  397. return function () {
  398. var emptyOptions = !(mergedSearchValue.value ? searchOptions.value : mergedOptions.value).length;
  399. var _props$dropdownMatchS = props.dropdownMatchSelectWidth,
  400. dropdownMatchSelectWidth = _props$dropdownMatchS === void 0 ? false : _props$dropdownMatchS;
  401. var dropdownStyle =
  402. // Search to match width
  403. mergedSearchValue.value && mergedSearchConfig.value.matchInputWidth ||
  404. // Empty keep the width
  405. emptyOptions ? {} : {
  406. minWidth: 'auto'
  407. };
  408. return _createVNode(BaseSelect, _objectSpread(_objectSpread(_objectSpread({}, pickProps.value), attrs), {}, {
  409. "ref": selectRef,
  410. "id": mergedId,
  411. "prefixCls": props.prefixCls,
  412. "dropdownMatchSelectWidth": dropdownMatchSelectWidth,
  413. "dropdownStyle": _objectSpread(_objectSpread({}, mergedDropdownStyle.value), dropdownStyle),
  414. "displayValues": displayValues.value,
  415. "onDisplayValuesChange": onDisplayValuesChange,
  416. "mode": multiple.value ? 'multiple' : undefined,
  417. "searchValue": mergedSearchValue.value,
  418. "onSearch": onInternalSearch,
  419. "showSearch": mergedShowSearch.value,
  420. "OptionList": OptionList,
  421. "emptyOptions": emptyOptions,
  422. "open": mergedOpen.value,
  423. "dropdownClassName": mergedDropdownClassName.value,
  424. "placement": mergedPlacement.value,
  425. "onDropdownVisibleChange": onInternalDropdownVisibleChange,
  426. "getRawInputElement": function getRawInputElement() {
  427. var _slots$default;
  428. return (_slots$default = slots.default) === null || _slots$default === void 0 ? void 0 : _slots$default.call(slots);
  429. }
  430. }), slots);
  431. };
  432. }
  433. });