useSelect.mjs 24 KB

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