| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663 |
- import { reactive, ref, computed, watch, watchEffect, nextTick, onMounted } from 'vue';
- import { castArray, isEqual, get, debounce, findLastIndex } from 'lodash-unified';
- import { isIOS, isClient, useResizeObserver } from '@vueuse/core';
- import { useLocale } from '../../../hooks/use-locale/index.mjs';
- import { useId } from '../../../hooks/use-id/index.mjs';
- import { useNamespace } from '../../../hooks/use-namespace/index.mjs';
- import { useFormItem, useFormItemInputId } from '../../form/src/hooks/use-form-item.mjs';
- import { useEmptyValues } from '../../../hooks/use-empty-values/index.mjs';
- import { useComposition } from '../../../hooks/use-composition/index.mjs';
- import { useFocusController } from '../../../hooks/use-focus-controller/index.mjs';
- import { debugWarn } from '../../../utils/error.mjs';
- import { isArray, isFunction, isPlainObject, isObject } from '@vue/shared';
- import { ValidateComponentsMap } from '../../../utils/vue/icon.mjs';
- import { useFormSize } from '../../form/src/hooks/use-form-common-props.mjs';
- import { isUndefined, isNumber } from '../../../utils/types.mjs';
- import { EVENT_CODE } from '../../../constants/aria.mjs';
- import { UPDATE_MODEL_EVENT, CHANGE_EVENT } from '../../../constants/event.mjs';
- import { scrollIntoView } from '../../../utils/dom/scroll.mjs';
- import { MINIMUM_INPUT_WIDTH } from '../../../constants/form.mjs';
- const useSelect = (props, emit) => {
- const { t } = useLocale();
- const contentId = useId();
- const nsSelect = useNamespace("select");
- const nsInput = useNamespace("input");
- const states = reactive({
- inputValue: "",
- options: /* @__PURE__ */ new Map(),
- cachedOptions: /* @__PURE__ */ new Map(),
- optionValues: [],
- selected: [],
- selectionWidth: 0,
- collapseItemWidth: 0,
- selectedLabel: "",
- hoveringIndex: -1,
- previousQuery: null,
- inputHovering: false,
- menuVisibleOnFocus: false,
- isBeforeHide: false
- });
- const selectRef = ref();
- const selectionRef = ref();
- const tooltipRef = ref();
- const tagTooltipRef = ref();
- const inputRef = ref();
- const prefixRef = ref();
- const suffixRef = ref();
- const menuRef = ref();
- const tagMenuRef = ref();
- const collapseItemRef = ref();
- const scrollbarRef = ref();
- const expanded = ref(false);
- const hoverOption = ref();
- const { form, formItem } = useFormItem();
- const { inputId } = useFormItemInputId(props, {
- formItemContext: formItem
- });
- const { valueOnClear, isEmptyValue } = useEmptyValues(props);
- const {
- isComposing,
- handleCompositionStart,
- handleCompositionUpdate,
- handleCompositionEnd
- } = useComposition({
- afterComposition: (e) => onInput(e)
- });
- const selectDisabled = computed(() => props.disabled || !!(form == null ? void 0 : form.disabled));
- const { wrapperRef, isFocused, handleBlur } = useFocusController(inputRef, {
- disabled: selectDisabled,
- afterFocus() {
- if (props.automaticDropdown && !expanded.value) {
- expanded.value = true;
- states.menuVisibleOnFocus = true;
- }
- },
- beforeBlur(event) {
- var _a, _b;
- return ((_a = tooltipRef.value) == null ? void 0 : _a.isFocusInsideContent(event)) || ((_b = tagTooltipRef.value) == null ? void 0 : _b.isFocusInsideContent(event));
- },
- afterBlur() {
- var _a;
- expanded.value = false;
- states.menuVisibleOnFocus = false;
- if (props.validateEvent) {
- (_a = formItem == null ? void 0 : formItem.validate) == null ? void 0 : _a.call(formItem, "blur").catch((err) => debugWarn());
- }
- }
- });
- const hasModelValue = computed(() => {
- return isArray(props.modelValue) ? props.modelValue.length > 0 : !isEmptyValue(props.modelValue);
- });
- const needStatusIcon = computed(() => {
- var _a;
- return (_a = form == null ? void 0 : form.statusIcon) != null ? _a : false;
- });
- const showClearBtn = computed(() => {
- return props.clearable && !selectDisabled.value && hasModelValue.value && (isFocused.value || states.inputHovering);
- });
- const iconComponent = computed(() => props.remote && props.filterable && !props.remoteShowSuffix ? "" : props.suffixIcon);
- const iconReverse = computed(() => nsSelect.is("reverse", !!(iconComponent.value && expanded.value)));
- const validateState = computed(() => (formItem == null ? void 0 : formItem.validateState) || "");
- const validateIcon = computed(() => validateState.value && ValidateComponentsMap[validateState.value]);
- const debounce$1 = computed(() => props.remote ? 300 : 0);
- const isRemoteSearchEmpty = computed(() => props.remote && !states.inputValue && states.options.size === 0);
- const emptyText = computed(() => {
- if (props.loading) {
- return props.loadingText || t("el.select.loading");
- } else {
- if (props.filterable && states.inputValue && states.options.size > 0 && filteredOptionsCount.value === 0) {
- return props.noMatchText || t("el.select.noMatch");
- }
- if (states.options.size === 0) {
- return props.noDataText || t("el.select.noData");
- }
- }
- return null;
- });
- const filteredOptionsCount = computed(() => optionsArray.value.filter((option) => option.visible).length);
- const optionsArray = computed(() => {
- const list = Array.from(states.options.values());
- const newList = [];
- states.optionValues.forEach((item) => {
- const index = list.findIndex((i) => i.value === item);
- if (index > -1) {
- newList.push(list[index]);
- }
- });
- return newList.length >= list.length ? newList : list;
- });
- const cachedOptionsArray = computed(() => Array.from(states.cachedOptions.values()));
- const showNewOption = computed(() => {
- const hasExistingOption = optionsArray.value.filter((option) => {
- return !option.created;
- }).some((option) => {
- return option.currentLabel === states.inputValue;
- });
- return props.filterable && props.allowCreate && states.inputValue !== "" && !hasExistingOption;
- });
- const updateOptions = () => {
- if (props.filterable && isFunction(props.filterMethod))
- return;
- if (props.filterable && props.remote && isFunction(props.remoteMethod))
- return;
- optionsArray.value.forEach((option) => {
- var _a;
- (_a = option.updateOption) == null ? void 0 : _a.call(option, states.inputValue);
- });
- };
- const selectSize = useFormSize();
- const collapseTagSize = computed(() => ["small"].includes(selectSize.value) ? "small" : "default");
- const dropdownMenuVisible = computed({
- get() {
- return expanded.value && !isRemoteSearchEmpty.value;
- },
- set(val) {
- expanded.value = val;
- }
- });
- const shouldShowPlaceholder = computed(() => {
- if (props.multiple && !isUndefined(props.modelValue)) {
- return castArray(props.modelValue).length === 0 && !states.inputValue;
- }
- const value = isArray(props.modelValue) ? props.modelValue[0] : props.modelValue;
- return props.filterable || isUndefined(value) ? !states.inputValue : true;
- });
- const currentPlaceholder = computed(() => {
- var _a;
- const _placeholder = (_a = props.placeholder) != null ? _a : t("el.select.placeholder");
- return props.multiple || !hasModelValue.value ? _placeholder : states.selectedLabel;
- });
- const mouseEnterEventName = computed(() => isIOS ? null : "mouseenter");
- watch(() => props.modelValue, (val, oldVal) => {
- if (props.multiple) {
- if (props.filterable && !props.reserveKeyword) {
- states.inputValue = "";
- handleQueryChange("");
- }
- }
- setSelected();
- if (!isEqual(val, oldVal) && props.validateEvent) {
- formItem == null ? void 0 : formItem.validate("change").catch((err) => debugWarn());
- }
- }, {
- flush: "post",
- deep: true
- });
- watch(() => expanded.value, (val) => {
- if (val) {
- handleQueryChange(states.inputValue);
- } else {
- states.inputValue = "";
- states.previousQuery = null;
- states.isBeforeHide = true;
- }
- emit("visible-change", val);
- });
- watch(() => states.options.entries(), () => {
- if (!isClient)
- return;
- setSelected();
- if (props.defaultFirstOption && (props.filterable || props.remote) && filteredOptionsCount.value) {
- checkDefaultFirstOption();
- }
- }, {
- flush: "post"
- });
- watch([() => states.hoveringIndex, optionsArray], ([val]) => {
- if (isNumber(val) && val > -1) {
- hoverOption.value = optionsArray.value[val] || {};
- } else {
- hoverOption.value = {};
- }
- optionsArray.value.forEach((option) => {
- option.hover = hoverOption.value === option;
- });
- });
- watchEffect(() => {
- if (states.isBeforeHide)
- return;
- updateOptions();
- });
- const handleQueryChange = (val) => {
- if (states.previousQuery === val || isComposing.value) {
- return;
- }
- states.previousQuery = val;
- if (props.filterable && isFunction(props.filterMethod)) {
- props.filterMethod(val);
- } else if (props.filterable && props.remote && isFunction(props.remoteMethod)) {
- props.remoteMethod(val);
- }
- if (props.defaultFirstOption && (props.filterable || props.remote) && filteredOptionsCount.value) {
- nextTick(checkDefaultFirstOption);
- } else {
- nextTick(updateHoveringIndex);
- }
- };
- const checkDefaultFirstOption = () => {
- const optionsInDropdown = optionsArray.value.filter((n) => n.visible && !n.disabled && !n.states.groupDisabled);
- const userCreatedOption = optionsInDropdown.find((n) => n.created);
- const firstOriginOption = optionsInDropdown[0];
- const valueList = optionsArray.value.map((item) => item.value);
- states.hoveringIndex = getValueIndex(valueList, userCreatedOption || firstOriginOption);
- };
- const setSelected = () => {
- if (!props.multiple) {
- const value = isArray(props.modelValue) ? props.modelValue[0] : props.modelValue;
- const option = getOption(value);
- states.selectedLabel = option.currentLabel;
- states.selected = [option];
- return;
- } else {
- states.selectedLabel = "";
- }
- const result = [];
- if (!isUndefined(props.modelValue)) {
- castArray(props.modelValue).forEach((value) => {
- result.push(getOption(value));
- });
- }
- states.selected = result;
- };
- const getOption = (value) => {
- let option;
- const isObjectValue = isPlainObject(value);
- for (let i = states.cachedOptions.size - 1; i >= 0; i--) {
- const cachedOption = cachedOptionsArray.value[i];
- const isEqualValue = isObjectValue ? get(cachedOption.value, props.valueKey) === get(value, props.valueKey) : cachedOption.value === value;
- if (isEqualValue) {
- option = {
- index: optionsArray.value.filter((opt) => !opt.created).indexOf(cachedOption),
- value,
- currentLabel: cachedOption.currentLabel,
- get isDisabled() {
- return cachedOption.isDisabled;
- }
- };
- break;
- }
- }
- if (option)
- return option;
- const label = isObjectValue ? value.label : value != null ? value : "";
- const newOption = {
- index: -1,
- value,
- currentLabel: label
- };
- return newOption;
- };
- const updateHoveringIndex = () => {
- states.hoveringIndex = optionsArray.value.findIndex((item) => states.selected.some((selected) => getValueKey(selected) === getValueKey(item)));
- };
- const resetSelectionWidth = () => {
- states.selectionWidth = Number.parseFloat(window.getComputedStyle(selectionRef.value).width);
- };
- const resetCollapseItemWidth = () => {
- states.collapseItemWidth = collapseItemRef.value.getBoundingClientRect().width;
- };
- const updateTooltip = () => {
- var _a, _b;
- (_b = (_a = tooltipRef.value) == null ? void 0 : _a.updatePopper) == null ? void 0 : _b.call(_a);
- };
- const updateTagTooltip = () => {
- var _a, _b;
- (_b = (_a = tagTooltipRef.value) == null ? void 0 : _a.updatePopper) == null ? void 0 : _b.call(_a);
- };
- const onInputChange = () => {
- if (states.inputValue.length > 0 && !expanded.value) {
- expanded.value = true;
- }
- handleQueryChange(states.inputValue);
- };
- const onInput = (event) => {
- states.inputValue = event.target.value;
- if (props.remote) {
- debouncedOnInputChange();
- } else {
- return onInputChange();
- }
- };
- const debouncedOnInputChange = debounce(() => {
- onInputChange();
- }, debounce$1.value);
- const emitChange = (val) => {
- if (!isEqual(props.modelValue, val)) {
- emit(CHANGE_EVENT, val);
- }
- };
- const getLastNotDisabledIndex = (value) => findLastIndex(value, (it) => {
- const option = states.cachedOptions.get(it);
- return option && !option.disabled && !option.states.groupDisabled;
- });
- const deletePrevTag = (e) => {
- if (!props.multiple)
- return;
- if (e.code === EVENT_CODE.delete)
- return;
- if (e.target.value.length <= 0) {
- const value = castArray(props.modelValue).slice();
- const lastNotDisabledIndex = getLastNotDisabledIndex(value);
- if (lastNotDisabledIndex < 0)
- return;
- const removeTagValue = value[lastNotDisabledIndex];
- value.splice(lastNotDisabledIndex, 1);
- emit(UPDATE_MODEL_EVENT, value);
- emitChange(value);
- emit("remove-tag", removeTagValue);
- }
- };
- const deleteTag = (event, tag) => {
- const index = states.selected.indexOf(tag);
- if (index > -1 && !selectDisabled.value) {
- const value = castArray(props.modelValue).slice();
- value.splice(index, 1);
- emit(UPDATE_MODEL_EVENT, value);
- emitChange(value);
- emit("remove-tag", tag.value);
- }
- event.stopPropagation();
- focus();
- };
- const deleteSelected = (event) => {
- event.stopPropagation();
- const value = props.multiple ? [] : valueOnClear.value;
- if (props.multiple) {
- for (const item of states.selected) {
- if (item.isDisabled)
- value.push(item.value);
- }
- }
- emit(UPDATE_MODEL_EVENT, value);
- emitChange(value);
- states.hoveringIndex = -1;
- expanded.value = false;
- emit("clear");
- focus();
- };
- const handleOptionSelect = (option) => {
- var _a;
- if (props.multiple) {
- const value = castArray((_a = props.modelValue) != null ? _a : []).slice();
- const optionIndex = getValueIndex(value, option);
- if (optionIndex > -1) {
- value.splice(optionIndex, 1);
- } else if (props.multipleLimit <= 0 || value.length < props.multipleLimit) {
- value.push(option.value);
- }
- emit(UPDATE_MODEL_EVENT, value);
- emitChange(value);
- if (option.created) {
- handleQueryChange("");
- }
- if (props.filterable && !props.reserveKeyword) {
- states.inputValue = "";
- }
- } else {
- !isEqual(props.modelValue, option.value) && emit(UPDATE_MODEL_EVENT, option.value);
- emitChange(option.value);
- expanded.value = false;
- }
- focus();
- if (expanded.value)
- return;
- nextTick(() => {
- scrollToOption(option);
- });
- };
- const getValueIndex = (arr, option) => {
- if (isUndefined(option))
- return -1;
- if (!isObject(option.value))
- return arr.indexOf(option.value);
- return arr.findIndex((item) => {
- return isEqual(get(item, props.valueKey), getValueKey(option));
- });
- };
- const scrollToOption = (option) => {
- var _a, _b, _c, _d, _e;
- const targetOption = isArray(option) ? option[0] : option;
- let target = null;
- if (targetOption == null ? void 0 : targetOption.value) {
- const options = optionsArray.value.filter((item) => item.value === targetOption.value);
- if (options.length > 0) {
- target = options[0].$el;
- }
- }
- if (tooltipRef.value && target) {
- const menu = (_d = (_c = (_b = (_a = tooltipRef.value) == null ? void 0 : _a.popperRef) == null ? void 0 : _b.contentRef) == null ? void 0 : _c.querySelector) == null ? void 0 : _d.call(_c, `.${nsSelect.be("dropdown", "wrap")}`);
- if (menu) {
- scrollIntoView(menu, target);
- }
- }
- (_e = scrollbarRef.value) == null ? void 0 : _e.handleScroll();
- };
- const onOptionCreate = (vm) => {
- states.options.set(vm.value, vm);
- states.cachedOptions.set(vm.value, vm);
- };
- const onOptionDestroy = (key, vm) => {
- if (states.options.get(key) === vm) {
- states.options.delete(key);
- }
- };
- const popperRef = computed(() => {
- var _a, _b;
- return (_b = (_a = tooltipRef.value) == null ? void 0 : _a.popperRef) == null ? void 0 : _b.contentRef;
- });
- const handleMenuEnter = () => {
- states.isBeforeHide = false;
- nextTick(() => {
- var _a;
- (_a = scrollbarRef.value) == null ? void 0 : _a.update();
- scrollToOption(states.selected);
- });
- };
- const focus = () => {
- var _a;
- (_a = inputRef.value) == null ? void 0 : _a.focus();
- };
- const blur = () => {
- var _a;
- if (expanded.value) {
- expanded.value = false;
- nextTick(() => {
- var _a2;
- return (_a2 = inputRef.value) == null ? void 0 : _a2.blur();
- });
- return;
- }
- (_a = inputRef.value) == null ? void 0 : _a.blur();
- };
- const handleClearClick = (event) => {
- deleteSelected(event);
- };
- const handleClickOutside = (event) => {
- expanded.value = false;
- if (isFocused.value) {
- const _event = new FocusEvent("blur", event);
- nextTick(() => handleBlur(_event));
- }
- };
- const handleEsc = () => {
- if (states.inputValue.length > 0) {
- states.inputValue = "";
- } else {
- expanded.value = false;
- }
- };
- const toggleMenu = () => {
- if (selectDisabled.value)
- return;
- if (isIOS)
- states.inputHovering = true;
- if (states.menuVisibleOnFocus) {
- states.menuVisibleOnFocus = false;
- } else {
- expanded.value = !expanded.value;
- }
- };
- const selectOption = () => {
- if (!expanded.value) {
- toggleMenu();
- } else {
- const option = optionsArray.value[states.hoveringIndex];
- if (option && !option.isDisabled) {
- handleOptionSelect(option);
- }
- }
- };
- const getValueKey = (item) => {
- return isObject(item.value) ? get(item.value, props.valueKey) : item.value;
- };
- const optionsAllDisabled = computed(() => optionsArray.value.filter((option) => option.visible).every((option) => option.isDisabled));
- const showTagList = computed(() => {
- if (!props.multiple) {
- return [];
- }
- return props.collapseTags ? states.selected.slice(0, props.maxCollapseTags) : states.selected;
- });
- const collapseTagList = computed(() => {
- if (!props.multiple) {
- return [];
- }
- return props.collapseTags ? states.selected.slice(props.maxCollapseTags) : [];
- });
- const navigateOptions = (direction) => {
- if (!expanded.value) {
- expanded.value = true;
- return;
- }
- if (states.options.size === 0 || filteredOptionsCount.value === 0 || isComposing.value)
- return;
- if (!optionsAllDisabled.value) {
- if (direction === "next") {
- states.hoveringIndex++;
- if (states.hoveringIndex === states.options.size) {
- states.hoveringIndex = 0;
- }
- } else if (direction === "prev") {
- states.hoveringIndex--;
- if (states.hoveringIndex < 0) {
- states.hoveringIndex = states.options.size - 1;
- }
- }
- const option = optionsArray.value[states.hoveringIndex];
- if (option.isDisabled || !option.visible) {
- navigateOptions(direction);
- }
- nextTick(() => scrollToOption(hoverOption.value));
- }
- };
- const getGapWidth = () => {
- if (!selectionRef.value)
- return 0;
- const style = window.getComputedStyle(selectionRef.value);
- return Number.parseFloat(style.gap || "6px");
- };
- const tagStyle = computed(() => {
- const gapWidth = getGapWidth();
- const inputSlotWidth = props.filterable ? gapWidth + MINIMUM_INPUT_WIDTH : 0;
- const maxWidth = collapseItemRef.value && props.maxCollapseTags === 1 ? states.selectionWidth - states.collapseItemWidth - gapWidth - inputSlotWidth : states.selectionWidth - inputSlotWidth;
- return { maxWidth: `${maxWidth}px` };
- });
- const collapseTagStyle = computed(() => {
- return { maxWidth: `${states.selectionWidth}px` };
- });
- const popupScroll = (data) => {
- emit("popup-scroll", data);
- };
- useResizeObserver(selectionRef, resetSelectionWidth);
- useResizeObserver(wrapperRef, updateTooltip);
- useResizeObserver(tagMenuRef, updateTagTooltip);
- useResizeObserver(collapseItemRef, resetCollapseItemWidth);
- let stop;
- watch(() => dropdownMenuVisible.value, (newVal) => {
- if (newVal) {
- stop = useResizeObserver(menuRef, updateTooltip).stop;
- } else {
- stop == null ? void 0 : stop();
- stop = void 0;
- }
- });
- onMounted(() => {
- setSelected();
- });
- return {
- inputId,
- contentId,
- nsSelect,
- nsInput,
- states,
- isFocused,
- expanded,
- optionsArray,
- hoverOption,
- selectSize,
- filteredOptionsCount,
- updateTooltip,
- updateTagTooltip,
- debouncedOnInputChange,
- onInput,
- deletePrevTag,
- deleteTag,
- deleteSelected,
- handleOptionSelect,
- scrollToOption,
- hasModelValue,
- shouldShowPlaceholder,
- currentPlaceholder,
- mouseEnterEventName,
- needStatusIcon,
- showClearBtn,
- iconComponent,
- iconReverse,
- validateState,
- validateIcon,
- showNewOption,
- updateOptions,
- collapseTagSize,
- setSelected,
- selectDisabled,
- emptyText,
- handleCompositionStart,
- handleCompositionUpdate,
- handleCompositionEnd,
- onOptionCreate,
- onOptionDestroy,
- handleMenuEnter,
- focus,
- blur,
- handleClearClick,
- handleClickOutside,
- handleEsc,
- toggleMenu,
- selectOption,
- getValueKey,
- navigateOptions,
- dropdownMenuVisible,
- showTagList,
- collapseTagList,
- popupScroll,
- getOption,
- tagStyle,
- collapseTagStyle,
- popperRef,
- inputRef,
- tooltipRef,
- tagTooltipRef,
- prefixRef,
- suffixRef,
- selectRef,
- wrapperRef,
- selectionRef,
- scrollbarRef,
- menuRef,
- tagMenuRef,
- collapseItemRef
- };
- };
- export { useSelect };
- //# sourceMappingURL=useSelect.mjs.map
|