useSelect.mjs 23 KB

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