useSelect.mjs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777
  1. import { reactive, ref, computed, nextTick, watch, watchEffect, onMounted } from 'vue';
  2. import { get, isEqual, findLastIndex } from 'lodash-unified';
  3. import { useDebounceFn, useResizeObserver } from '@vueuse/core';
  4. import { useAllowCreate } from './useAllowCreate.mjs';
  5. import { useProps } from './useProps.mjs';
  6. import { useLocale } from '../../../hooks/use-locale/index.mjs';
  7. import { useNamespace } from '../../../hooks/use-namespace/index.mjs';
  8. import { useFormItem, useFormItemInputId } from '../../form/src/hooks/use-form-item.mjs';
  9. import { useEmptyValues } from '../../../hooks/use-empty-values/index.mjs';
  10. import { useComposition } from '../../../hooks/use-composition/index.mjs';
  11. import { useFocusController } from '../../../hooks/use-focus-controller/index.mjs';
  12. import { debugWarn } from '../../../utils/error.mjs';
  13. import { isArray, isFunction, isObject } from '@vue/shared';
  14. import { ValidateComponentsMap } from '../../../utils/vue/icon.mjs';
  15. import { escapeStringRegexp } from '../../../utils/strings.mjs';
  16. import { useFormSize } from '../../form/src/hooks/use-form-common-props.mjs';
  17. import { MINIMUM_INPUT_WIDTH } from '../../../constants/form.mjs';
  18. import { isEmpty, isUndefined, isNumber } from '../../../utils/types.mjs';
  19. import { getEventCode } from '../../../utils/dom/event.mjs';
  20. import { EVENT_CODE } from '../../../constants/aria.mjs';
  21. import { UPDATE_MODEL_EVENT, CHANGE_EVENT } from '../../../constants/event.mjs';
  22. const useSelect = (props, emit) => {
  23. const { t } = useLocale();
  24. const nsSelect = useNamespace("select");
  25. const nsInput = useNamespace("input");
  26. const { form: elForm, formItem: elFormItem } = useFormItem();
  27. const { inputId } = useFormItemInputId(props, {
  28. formItemContext: elFormItem
  29. });
  30. const { aliasProps, getLabel, getValue, getDisabled, getOptions } = useProps(props);
  31. const { valueOnClear, isEmptyValue } = useEmptyValues(props);
  32. const states = reactive({
  33. inputValue: "",
  34. cachedOptions: [],
  35. createdOptions: [],
  36. hoveringIndex: -1,
  37. inputHovering: false,
  38. selectionWidth: 0,
  39. collapseItemWidth: 0,
  40. previousQuery: null,
  41. previousValue: void 0,
  42. selectedLabel: "",
  43. menuVisibleOnFocus: false,
  44. isBeforeHide: false
  45. });
  46. const popperSize = ref(-1);
  47. const debouncing = ref(false);
  48. const selectRef = ref();
  49. const selectionRef = ref();
  50. const tooltipRef = ref();
  51. const tagTooltipRef = ref();
  52. const inputRef = ref();
  53. const prefixRef = ref();
  54. const suffixRef = ref();
  55. const menuRef = ref();
  56. const tagMenuRef = ref();
  57. const collapseItemRef = ref();
  58. const {
  59. isComposing,
  60. handleCompositionStart,
  61. handleCompositionEnd,
  62. handleCompositionUpdate
  63. } = useComposition({
  64. afterComposition: (e) => onInput(e)
  65. });
  66. const selectDisabled = computed(() => props.disabled || !!(elForm == null ? void 0 : elForm.disabled));
  67. const { wrapperRef, isFocused, handleBlur } = useFocusController(inputRef, {
  68. disabled: selectDisabled,
  69. afterFocus() {
  70. if (props.automaticDropdown && !expanded.value) {
  71. expanded.value = true;
  72. states.menuVisibleOnFocus = true;
  73. }
  74. },
  75. beforeBlur(event) {
  76. var _a, _b;
  77. return ((_a = tooltipRef.value) == null ? void 0 : _a.isFocusInsideContent(event)) || ((_b = tagTooltipRef.value) == null ? void 0 : _b.isFocusInsideContent(event));
  78. },
  79. afterBlur() {
  80. var _a;
  81. expanded.value = false;
  82. states.menuVisibleOnFocus = false;
  83. if (props.validateEvent) {
  84. (_a = elFormItem == null ? void 0 : elFormItem.validate) == null ? void 0 : _a.call(elFormItem, "blur").catch((err) => debugWarn(err));
  85. }
  86. }
  87. });
  88. const allOptions = computed(() => filterOptions(""));
  89. const hasOptions = computed(() => {
  90. if (props.loading)
  91. return false;
  92. return props.options.length > 0 || states.createdOptions.length > 0;
  93. });
  94. const filteredOptions = ref([]);
  95. const expanded = ref(false);
  96. const needStatusIcon = computed(() => {
  97. var _a;
  98. return (_a = elForm == null ? void 0 : elForm.statusIcon) != null ? _a : false;
  99. });
  100. const popupHeight = computed(() => {
  101. const totalHeight = filteredOptions.value.length * props.itemHeight;
  102. return totalHeight > props.height ? props.height : totalHeight;
  103. });
  104. const hasModelValue = computed(() => {
  105. return props.multiple ? isArray(props.modelValue) && props.modelValue.length > 0 : !isEmptyValue(props.modelValue);
  106. });
  107. const showClearBtn = computed(() => {
  108. return props.clearable && !selectDisabled.value && hasModelValue.value && (isFocused.value || states.inputHovering);
  109. });
  110. const iconComponent = computed(() => props.remote && props.filterable ? "" : props.suffixIcon);
  111. const iconReverse = computed(() => iconComponent.value && nsSelect.is("reverse", expanded.value));
  112. const validateState = computed(() => (elFormItem == null ? void 0 : elFormItem.validateState) || "");
  113. const validateIcon = computed(() => {
  114. if (!validateState.value)
  115. return;
  116. return ValidateComponentsMap[validateState.value];
  117. });
  118. const debounce = computed(() => props.remote ? props.debounce : 0);
  119. const isRemoteSearchEmpty = computed(() => props.remote && !states.inputValue && !hasOptions.value);
  120. const emptyText = computed(() => {
  121. if (props.loading) {
  122. return props.loadingText || t("el.select.loading");
  123. } else {
  124. if (props.filterable && states.inputValue && hasOptions.value && filteredOptions.value.length === 0) {
  125. return props.noMatchText || t("el.select.noMatch");
  126. }
  127. if (!hasOptions.value) {
  128. return props.noDataText || t("el.select.noData");
  129. }
  130. }
  131. return null;
  132. });
  133. const isFilterMethodValid = computed(() => props.filterable && isFunction(props.filterMethod));
  134. const isRemoteMethodValid = computed(() => props.filterable && props.remote && isFunction(props.remoteMethod));
  135. const filterOptions = (query) => {
  136. const regexp = new RegExp(escapeStringRegexp(query), "i");
  137. const isValidOption = (o) => {
  138. if (isFilterMethodValid.value || isRemoteMethodValid.value)
  139. return true;
  140. return query ? regexp.test(getLabel(o) || "") : true;
  141. };
  142. if (props.loading) {
  143. return [];
  144. }
  145. return [...states.createdOptions, ...props.options].reduce((all, item) => {
  146. const options = getOptions(item);
  147. if (isArray(options)) {
  148. const filtered = options.filter(isValidOption);
  149. if (filtered.length > 0) {
  150. all.push({
  151. label: getLabel(item),
  152. type: "Group"
  153. }, ...filtered);
  154. }
  155. } else if (props.remote || isValidOption(item)) {
  156. all.push(item);
  157. }
  158. return all;
  159. }, []);
  160. };
  161. const updateOptions = () => {
  162. filteredOptions.value = filterOptions(states.inputValue);
  163. };
  164. const allOptionsValueMap = computed(() => {
  165. const valueMap = /* @__PURE__ */ new Map();
  166. allOptions.value.forEach((option, index) => {
  167. valueMap.set(getValueKey(getValue(option)), { option, index });
  168. });
  169. return valueMap;
  170. });
  171. const filteredOptionsValueMap = computed(() => {
  172. const valueMap = /* @__PURE__ */ new Map();
  173. filteredOptions.value.forEach((option, index) => {
  174. valueMap.set(getValueKey(getValue(option)), { option, index });
  175. });
  176. return valueMap;
  177. });
  178. const optionsAllDisabled = computed(() => filteredOptions.value.every((option) => getDisabled(option)));
  179. const selectSize = useFormSize();
  180. const collapseTagSize = computed(() => selectSize.value === "small" ? "small" : "default");
  181. const calculatePopperSize = () => {
  182. var _a;
  183. if (isNumber(props.fitInputWidth)) {
  184. popperSize.value = props.fitInputWidth;
  185. return;
  186. }
  187. const width = ((_a = selectRef.value) == null ? void 0 : _a.offsetWidth) || 200;
  188. if (!props.fitInputWidth && hasOptions.value) {
  189. nextTick(() => {
  190. popperSize.value = Math.max(width, calculateLabelMaxWidth());
  191. });
  192. } else {
  193. popperSize.value = width;
  194. }
  195. };
  196. const calculateLabelMaxWidth = () => {
  197. var _a, _b;
  198. const canvas = document.createElement("canvas");
  199. const ctx = canvas.getContext("2d");
  200. const selector = nsSelect.be("dropdown", "item");
  201. const dom = ((_b = (_a = menuRef.value) == null ? void 0 : _a.listRef) == null ? void 0 : _b.innerRef) || document;
  202. const dropdownItemEl = dom.querySelector(`.${selector}`);
  203. if (dropdownItemEl === null || ctx === null)
  204. return 0;
  205. const style = getComputedStyle(dropdownItemEl);
  206. const padding = Number.parseFloat(style.paddingLeft) + Number.parseFloat(style.paddingRight);
  207. ctx.font = `bold ${style.font.replace(new RegExp(`\\b${style.fontWeight}\\b`), "")}`;
  208. const maxWidth = filteredOptions.value.reduce((max, option) => {
  209. const metrics = ctx.measureText(getLabel(option));
  210. return Math.max(metrics.width, max);
  211. }, 0);
  212. return maxWidth + padding;
  213. };
  214. const getGapWidth = () => {
  215. if (!selectionRef.value)
  216. return 0;
  217. const style = window.getComputedStyle(selectionRef.value);
  218. return Number.parseFloat(style.gap || "6px");
  219. };
  220. const tagStyle = computed(() => {
  221. const gapWidth = getGapWidth();
  222. const inputSlotWidth = props.filterable ? gapWidth + MINIMUM_INPUT_WIDTH : 0;
  223. const maxWidth = collapseItemRef.value && props.maxCollapseTags === 1 ? states.selectionWidth - states.collapseItemWidth - gapWidth - inputSlotWidth : states.selectionWidth - inputSlotWidth;
  224. return { maxWidth: `${maxWidth}px` };
  225. });
  226. const collapseTagStyle = computed(() => {
  227. return { maxWidth: `${states.selectionWidth}px` };
  228. });
  229. const shouldShowPlaceholder = computed(() => {
  230. if (isArray(props.modelValue)) {
  231. return props.modelValue.length === 0 && !states.inputValue;
  232. }
  233. return props.filterable ? !states.inputValue : true;
  234. });
  235. const currentPlaceholder = computed(() => {
  236. var _a;
  237. const _placeholder = (_a = props.placeholder) != null ? _a : t("el.select.placeholder");
  238. return props.multiple || !hasModelValue.value ? _placeholder : states.selectedLabel;
  239. });
  240. const popperRef = computed(() => {
  241. var _a, _b;
  242. return (_b = (_a = tooltipRef.value) == null ? void 0 : _a.popperRef) == null ? void 0 : _b.contentRef;
  243. });
  244. const indexRef = computed(() => {
  245. if (props.multiple) {
  246. const len = props.modelValue.length;
  247. if (props.modelValue.length > 0 && filteredOptionsValueMap.value.has(props.modelValue[len - 1])) {
  248. const { index } = filteredOptionsValueMap.value.get(props.modelValue[len - 1]);
  249. return index;
  250. }
  251. } else {
  252. if (!isEmptyValue(props.modelValue) && filteredOptionsValueMap.value.has(props.modelValue)) {
  253. const { index } = filteredOptionsValueMap.value.get(props.modelValue);
  254. return index;
  255. }
  256. }
  257. return -1;
  258. });
  259. const dropdownMenuVisible = computed({
  260. get() {
  261. return expanded.value && (props.loading || !isRemoteSearchEmpty.value) && (!debouncing.value || !isEmpty(states.previousQuery));
  262. },
  263. set(val) {
  264. expanded.value = val;
  265. }
  266. });
  267. const showTagList = computed(() => {
  268. if (!props.multiple) {
  269. return [];
  270. }
  271. return props.collapseTags ? states.cachedOptions.slice(0, props.maxCollapseTags) : states.cachedOptions;
  272. });
  273. const collapseTagList = computed(() => {
  274. if (!props.multiple) {
  275. return [];
  276. }
  277. return props.collapseTags ? states.cachedOptions.slice(props.maxCollapseTags) : [];
  278. });
  279. const {
  280. createNewOption,
  281. removeNewOption,
  282. selectNewOption,
  283. clearAllNewOption
  284. } = useAllowCreate(props, states);
  285. const toggleMenu = () => {
  286. if (selectDisabled.value)
  287. return;
  288. if (states.menuVisibleOnFocus) {
  289. states.menuVisibleOnFocus = false;
  290. } else {
  291. expanded.value = !expanded.value;
  292. }
  293. };
  294. const onInputChange = () => {
  295. if (states.inputValue.length > 0 && !expanded.value) {
  296. expanded.value = true;
  297. }
  298. createNewOption(states.inputValue);
  299. nextTick(() => {
  300. handleQueryChange(states.inputValue);
  301. });
  302. };
  303. const debouncedOnInputChange = useDebounceFn(() => {
  304. onInputChange();
  305. debouncing.value = false;
  306. }, debounce);
  307. const handleQueryChange = (val) => {
  308. if (states.previousQuery === val || isComposing.value) {
  309. return;
  310. }
  311. states.previousQuery = val;
  312. if (props.filterable && isFunction(props.filterMethod)) {
  313. props.filterMethod(val);
  314. } else if (props.filterable && props.remote && isFunction(props.remoteMethod)) {
  315. props.remoteMethod(val);
  316. }
  317. if (props.defaultFirstOption && (props.filterable || props.remote) && filteredOptions.value.length) {
  318. nextTick(checkDefaultFirstOption);
  319. } else {
  320. nextTick(updateHoveringIndex);
  321. }
  322. };
  323. const checkDefaultFirstOption = () => {
  324. const optionsInDropdown = filteredOptions.value.filter((n) => !n.disabled && n.type !== "Group");
  325. const userCreatedOption = optionsInDropdown.find((n) => n.created);
  326. const firstOriginOption = optionsInDropdown[0];
  327. states.hoveringIndex = getValueIndex(filteredOptions.value, userCreatedOption || firstOriginOption);
  328. };
  329. const emitChange = (val) => {
  330. if (!isEqual(props.modelValue, val)) {
  331. emit(CHANGE_EVENT, val);
  332. }
  333. };
  334. const update = (val) => {
  335. emit(UPDATE_MODEL_EVENT, val);
  336. emitChange(val);
  337. states.previousValue = props.multiple ? String(val) : val;
  338. nextTick(() => {
  339. if (props.multiple && isArray(props.modelValue)) {
  340. const cachedOptions = states.cachedOptions.slice();
  341. const selectedOptions = props.modelValue.map((value) => getOption(value, cachedOptions));
  342. if (!isEqual(states.cachedOptions, selectedOptions)) {
  343. states.cachedOptions = selectedOptions;
  344. }
  345. } else {
  346. initStates(true);
  347. }
  348. });
  349. };
  350. const getValueIndex = (arr = [], value) => {
  351. if (!isObject(value)) {
  352. return arr.indexOf(value);
  353. }
  354. const valueKey = props.valueKey;
  355. let index = -1;
  356. arr.some((item, i) => {
  357. if (get(item, valueKey) === get(value, valueKey)) {
  358. index = i;
  359. return true;
  360. }
  361. return false;
  362. });
  363. return index;
  364. };
  365. const getValueKey = (item) => {
  366. return isObject(item) ? get(item, props.valueKey) : item;
  367. };
  368. const handleResize = () => {
  369. calculatePopperSize();
  370. };
  371. const resetSelectionWidth = () => {
  372. states.selectionWidth = Number.parseFloat(window.getComputedStyle(selectionRef.value).width);
  373. };
  374. const resetCollapseItemWidth = () => {
  375. states.collapseItemWidth = collapseItemRef.value.getBoundingClientRect().width;
  376. };
  377. const updateTooltip = () => {
  378. var _a, _b;
  379. (_b = (_a = tooltipRef.value) == null ? void 0 : _a.updatePopper) == null ? void 0 : _b.call(_a);
  380. };
  381. const updateTagTooltip = () => {
  382. var _a, _b;
  383. (_b = (_a = tagTooltipRef.value) == null ? void 0 : _a.updatePopper) == null ? void 0 : _b.call(_a);
  384. };
  385. const onSelect = (option) => {
  386. const optionValue = getValue(option);
  387. if (props.multiple) {
  388. let selectedOptions = props.modelValue.slice();
  389. const index = getValueIndex(selectedOptions, optionValue);
  390. if (index > -1) {
  391. selectedOptions = [
  392. ...selectedOptions.slice(0, index),
  393. ...selectedOptions.slice(index + 1)
  394. ];
  395. states.cachedOptions.splice(index, 1);
  396. removeNewOption(option);
  397. } else if (props.multipleLimit <= 0 || selectedOptions.length < props.multipleLimit) {
  398. selectedOptions = [...selectedOptions, optionValue];
  399. states.cachedOptions.push(option);
  400. selectNewOption(option);
  401. }
  402. update(selectedOptions);
  403. if (option.created) {
  404. handleQueryChange("");
  405. }
  406. if (props.filterable && !props.reserveKeyword) {
  407. states.inputValue = "";
  408. }
  409. } else {
  410. states.selectedLabel = getLabel(option);
  411. !isEqual(props.modelValue, optionValue) && update(optionValue);
  412. expanded.value = false;
  413. selectNewOption(option);
  414. if (!option.created) {
  415. clearAllNewOption();
  416. }
  417. }
  418. focus();
  419. };
  420. const deleteTag = (event, option) => {
  421. let selectedOptions = props.modelValue.slice();
  422. const index = getValueIndex(selectedOptions, getValue(option));
  423. if (index > -1 && !selectDisabled.value) {
  424. selectedOptions = [
  425. ...props.modelValue.slice(0, index),
  426. ...props.modelValue.slice(index + 1)
  427. ];
  428. states.cachedOptions.splice(index, 1);
  429. update(selectedOptions);
  430. emit("remove-tag", getValue(option));
  431. removeNewOption(option);
  432. }
  433. event.stopPropagation();
  434. focus();
  435. };
  436. const focus = () => {
  437. var _a;
  438. (_a = inputRef.value) == null ? void 0 : _a.focus();
  439. };
  440. const blur = () => {
  441. var _a;
  442. if (expanded.value) {
  443. expanded.value = false;
  444. nextTick(() => {
  445. var _a2;
  446. return (_a2 = inputRef.value) == null ? void 0 : _a2.blur();
  447. });
  448. return;
  449. }
  450. (_a = inputRef.value) == null ? void 0 : _a.blur();
  451. };
  452. const handleEsc = () => {
  453. if (states.inputValue.length > 0) {
  454. states.inputValue = "";
  455. } else {
  456. expanded.value = false;
  457. }
  458. };
  459. const getLastNotDisabledIndex = (value) => findLastIndex(value, (it) => !states.cachedOptions.some((option) => getValue(option) === it && getDisabled(option)));
  460. const handleDel = (e) => {
  461. const code = getEventCode(e);
  462. if (!props.multiple)
  463. return;
  464. if (code === EVENT_CODE.delete)
  465. return;
  466. if (states.inputValue.length === 0) {
  467. e.preventDefault();
  468. const selected = props.modelValue.slice();
  469. const lastNotDisabledIndex = getLastNotDisabledIndex(selected);
  470. if (lastNotDisabledIndex < 0)
  471. return;
  472. const removeTagValue = selected[lastNotDisabledIndex];
  473. selected.splice(lastNotDisabledIndex, 1);
  474. const option = states.cachedOptions[lastNotDisabledIndex];
  475. states.cachedOptions.splice(lastNotDisabledIndex, 1);
  476. removeNewOption(option);
  477. update(selected);
  478. emit("remove-tag", removeTagValue);
  479. }
  480. };
  481. const handleClear = () => {
  482. let emptyValue;
  483. if (isArray(props.modelValue)) {
  484. emptyValue = [];
  485. } else {
  486. emptyValue = valueOnClear.value;
  487. }
  488. states.selectedLabel = "";
  489. expanded.value = false;
  490. update(emptyValue);
  491. emit("clear");
  492. clearAllNewOption();
  493. focus();
  494. };
  495. const onKeyboardNavigate = (direction, hoveringIndex = void 0) => {
  496. const options = filteredOptions.value;
  497. if (!["forward", "backward"].includes(direction) || selectDisabled.value || options.length <= 0 || optionsAllDisabled.value || isComposing.value) {
  498. return;
  499. }
  500. if (!expanded.value) {
  501. return toggleMenu();
  502. }
  503. if (isUndefined(hoveringIndex)) {
  504. hoveringIndex = states.hoveringIndex;
  505. }
  506. let newIndex = -1;
  507. if (direction === "forward") {
  508. newIndex = hoveringIndex + 1;
  509. if (newIndex >= options.length) {
  510. newIndex = 0;
  511. }
  512. } else if (direction === "backward") {
  513. newIndex = hoveringIndex - 1;
  514. if (newIndex < 0 || newIndex >= options.length) {
  515. newIndex = options.length - 1;
  516. }
  517. }
  518. const option = options[newIndex];
  519. if (getDisabled(option) || option.type === "Group") {
  520. return onKeyboardNavigate(direction, newIndex);
  521. } else {
  522. states.hoveringIndex = newIndex;
  523. scrollToItem(newIndex);
  524. }
  525. };
  526. const onKeyboardSelect = () => {
  527. if (!expanded.value) {
  528. return toggleMenu();
  529. } else if (~states.hoveringIndex && filteredOptions.value[states.hoveringIndex]) {
  530. onSelect(filteredOptions.value[states.hoveringIndex]);
  531. }
  532. };
  533. const onHoverOption = (idx) => {
  534. states.hoveringIndex = idx != null ? idx : -1;
  535. };
  536. const updateHoveringIndex = () => {
  537. if (!props.multiple) {
  538. states.hoveringIndex = filteredOptions.value.findIndex((item) => {
  539. return getValueKey(getValue(item)) === getValueKey(props.modelValue);
  540. });
  541. } else {
  542. states.hoveringIndex = filteredOptions.value.findIndex((item) => props.modelValue.some((modelValue) => getValueKey(modelValue) === getValueKey(getValue(item))));
  543. }
  544. };
  545. const onInput = (event) => {
  546. states.inputValue = event.target.value;
  547. if (props.remote) {
  548. debouncing.value = true;
  549. debouncedOnInputChange();
  550. } else {
  551. return onInputChange();
  552. }
  553. };
  554. const handleClickOutside = (event) => {
  555. expanded.value = false;
  556. if (isFocused.value) {
  557. const _event = new FocusEvent("blur", event);
  558. handleBlur(_event);
  559. }
  560. };
  561. const handleMenuEnter = () => {
  562. states.isBeforeHide = false;
  563. return nextTick(() => {
  564. if (~indexRef.value) {
  565. scrollToItem(states.hoveringIndex);
  566. }
  567. });
  568. };
  569. const scrollToItem = (index) => {
  570. menuRef.value.scrollToItem(index);
  571. };
  572. const getOption = (value, cachedOptions) => {
  573. const selectValue = getValueKey(value);
  574. if (allOptionsValueMap.value.has(selectValue)) {
  575. const { option } = allOptionsValueMap.value.get(selectValue);
  576. return option;
  577. }
  578. if (cachedOptions && cachedOptions.length) {
  579. const option = cachedOptions.find((option2) => getValueKey(getValue(option2)) === selectValue);
  580. if (option) {
  581. return option;
  582. }
  583. }
  584. return {
  585. [aliasProps.value.value]: value,
  586. [aliasProps.value.label]: value
  587. };
  588. };
  589. const getIndex = (option) => {
  590. var _a, _b;
  591. return (_b = (_a = allOptionsValueMap.value.get(getValue(option))) == null ? void 0 : _a.index) != null ? _b : -1;
  592. };
  593. const initStates = (needUpdateSelectedLabel = false) => {
  594. if (props.multiple) {
  595. if (props.modelValue.length > 0) {
  596. const cachedOptions = states.cachedOptions.slice();
  597. states.cachedOptions.length = 0;
  598. states.previousValue = props.modelValue.toString();
  599. for (const value of props.modelValue) {
  600. const option = getOption(value, cachedOptions);
  601. states.cachedOptions.push(option);
  602. }
  603. } else {
  604. states.cachedOptions = [];
  605. states.previousValue = void 0;
  606. }
  607. } else {
  608. if (hasModelValue.value) {
  609. states.previousValue = props.modelValue;
  610. const options = filteredOptions.value;
  611. const selectedItemIndex = options.findIndex((option) => getValueKey(getValue(option)) === getValueKey(props.modelValue));
  612. if (~selectedItemIndex) {
  613. states.selectedLabel = getLabel(options[selectedItemIndex]);
  614. } else {
  615. if (!states.selectedLabel || needUpdateSelectedLabel) {
  616. states.selectedLabel = getValueKey(props.modelValue);
  617. }
  618. }
  619. } else {
  620. states.selectedLabel = "";
  621. states.previousValue = void 0;
  622. }
  623. }
  624. clearAllNewOption();
  625. calculatePopperSize();
  626. };
  627. watch(() => props.fitInputWidth, () => {
  628. calculatePopperSize();
  629. });
  630. watch(expanded, (val) => {
  631. if (val) {
  632. if (!props.persistent) {
  633. calculatePopperSize();
  634. }
  635. handleQueryChange("");
  636. } else {
  637. states.inputValue = "";
  638. states.previousQuery = null;
  639. states.isBeforeHide = true;
  640. createNewOption("");
  641. }
  642. emit("visible-change", val);
  643. });
  644. watch(() => props.modelValue, (val, oldVal) => {
  645. var _a;
  646. const isValEmpty = !val || isArray(val) && val.length === 0;
  647. if (isValEmpty || props.multiple && !isEqual(val.toString(), states.previousValue) || !props.multiple && getValueKey(val) !== getValueKey(states.previousValue)) {
  648. initStates(true);
  649. }
  650. if (!isEqual(val, oldVal) && props.validateEvent) {
  651. (_a = elFormItem == null ? void 0 : elFormItem.validate) == null ? void 0 : _a.call(elFormItem, "change").catch((err) => debugWarn(err));
  652. }
  653. }, {
  654. deep: true
  655. });
  656. watch(() => props.options, () => {
  657. const input = inputRef.value;
  658. if (!input || input && document.activeElement !== input) {
  659. initStates();
  660. }
  661. }, {
  662. deep: true,
  663. flush: "post"
  664. });
  665. watch(() => filteredOptions.value, () => {
  666. calculatePopperSize();
  667. return menuRef.value && nextTick(menuRef.value.resetScrollTop);
  668. });
  669. watchEffect(() => {
  670. if (states.isBeforeHide)
  671. return;
  672. updateOptions();
  673. });
  674. watchEffect(() => {
  675. const { valueKey, options } = props;
  676. const duplicateValue = /* @__PURE__ */ new Map();
  677. for (const item of options) {
  678. const optionValue = getValue(item);
  679. let v = optionValue;
  680. if (isObject(v)) {
  681. v = get(optionValue, valueKey);
  682. }
  683. if (duplicateValue.get(v)) {
  684. debugWarn("ElSelectV2", `The option values you provided seem to be duplicated, which may cause some problems, please check.`);
  685. break;
  686. } else {
  687. duplicateValue.set(v, true);
  688. }
  689. }
  690. });
  691. onMounted(() => {
  692. initStates();
  693. });
  694. useResizeObserver(selectRef, handleResize);
  695. useResizeObserver(selectionRef, resetSelectionWidth);
  696. useResizeObserver(menuRef, updateTooltip);
  697. useResizeObserver(wrapperRef, updateTooltip);
  698. useResizeObserver(tagMenuRef, updateTagTooltip);
  699. useResizeObserver(collapseItemRef, resetCollapseItemWidth);
  700. return {
  701. inputId,
  702. collapseTagSize,
  703. currentPlaceholder,
  704. expanded,
  705. emptyText,
  706. popupHeight,
  707. debounce,
  708. allOptions,
  709. allOptionsValueMap,
  710. filteredOptions,
  711. iconComponent,
  712. iconReverse,
  713. tagStyle,
  714. collapseTagStyle,
  715. popperSize,
  716. dropdownMenuVisible,
  717. hasModelValue,
  718. shouldShowPlaceholder,
  719. selectDisabled,
  720. selectSize,
  721. needStatusIcon,
  722. showClearBtn,
  723. states,
  724. isFocused,
  725. nsSelect,
  726. nsInput,
  727. inputRef,
  728. menuRef,
  729. tagMenuRef,
  730. tooltipRef,
  731. tagTooltipRef,
  732. selectRef,
  733. wrapperRef,
  734. selectionRef,
  735. prefixRef,
  736. suffixRef,
  737. collapseItemRef,
  738. popperRef,
  739. validateState,
  740. validateIcon,
  741. showTagList,
  742. collapseTagList,
  743. debouncedOnInputChange,
  744. deleteTag,
  745. getLabel,
  746. getValue,
  747. getDisabled,
  748. getValueKey,
  749. getIndex,
  750. handleClear,
  751. handleClickOutside,
  752. handleDel,
  753. handleEsc,
  754. focus,
  755. blur,
  756. handleMenuEnter,
  757. handleResize,
  758. resetSelectionWidth,
  759. updateTooltip,
  760. updateTagTooltip,
  761. updateOptions,
  762. toggleMenu,
  763. scrollTo: scrollToItem,
  764. onInput,
  765. onKeyboardNavigate,
  766. onKeyboardSelect,
  767. onSelect,
  768. onHover: onHoverOption,
  769. handleCompositionStart,
  770. handleCompositionEnd,
  771. handleCompositionUpdate
  772. };
  773. };
  774. export { useSelect as default };
  775. //# sourceMappingURL=useSelect.mjs.map