UNPKG

@vuesax-alpha/nightly

Version:
748 lines (745 loc) • 22.4 kB
import { reactive, ref, shallowRef, computed, watch, nextTick, triggerRef } from 'vue'; import { isEqual, isNil, debounce, findLastIndex, isArray } from 'lodash-unified'; import '../../../constants/index.mjs'; import '../../../utils/index.mjs'; import '../../../hooks/index.mjs'; import { useNamespace } from '../../../hooks/use-namespace/index.mjs'; import { useLocale } from '../../../hooks/use-locale/index.mjs'; import { useId } from '../../../hooks/use-id/index.mjs'; import { isClient } from '@vueuse/core'; import { isFunction, isObject } from '@vue/shared'; import { EVENT_CODE } from '../../../constants/aria.mjs'; import { UPDATE_MODEL_EVENT } from '../../../constants/event.mjs'; import { setStyle, removeStyle } from '../../../utils/dom/style.mjs'; import { scrollIntoView } from '../../../utils/dom/scroll.mjs'; import { isBoolean } from '../../../utils/types.mjs'; import { isKorean } from '../../../utils/i18n.mjs'; function useSelectStates(props) { return reactive({ options: /* @__PURE__ */ new Map(), cachedOptions: /* @__PURE__ */ new Map(), selected: /* @__PURE__ */ new Map(), disabledOptions: /* @__PURE__ */ new Map(), createdLabel: null, targetOnElement: null, createdSelected: false, optionsCount: 0, filteredOptionsCount: 0, visible: false, softFocus: false, selectedLabel: "", hoverIndex: -1, query: "", previousQuery: null, cachedPlaceHolder: "", currentPlaceholder: props.placeholder, menuVisibleOnFocus: false, isOnComposition: false, isSilentBlur: false, mouseEnter: false }); } const useSelect = (props, states, emit) => { const ns = useNamespace("select"); const { t } = useLocale(); const reference = ref(); const input = ref(); const popperRef = ref(); const chips = ref(); const selectWrapper = ref(); const scrollbar = ref(); const hoverOption = ref(); const inputId = useId(props.id); const queryChange = shallowRef(""); const debounce$1 = ref(0); const readonly = computed( () => !props.filter || props.multiple || !states.visible ); const selectDisabled = computed(() => props.disabled); const showClose = computed(() => { const hasValue = props.multiple ? Array.isArray(props.modelValue) && props.modelValue.length > 0 : props.modelValue !== void 0 && props.modelValue !== null && props.modelValue !== ""; const criteria = props.clearable && !selectDisabled.value && states.mouseEnter && hasValue; return criteria; }); const optionsArray = computed(() => Array.from(states.options.values())); const cachedOptionsArray = computed( () => Array.from(states.cachedOptions.values()) ); const selectedArray = computed(() => Array.from(states.selected.values())); const showNewOption = computed(() => { const hasExistingOption = optionsArray.value.filter((option) => { return !option.created; }).some((option) => { return option.currentLabel === states.query; }); return props.filter && props.allowCreate && states.query !== "" && !hasExistingOption; }); const dropMenuVisible = computed({ get() { return states.visible; }, set(val) { states.visible = val; } }); const emptyText = computed(() => { if (props.loading) { return props.loadingText || t("vs.select.loading"); } if (props.filter && states.query && states.options.size > 0 && states.filteredOptionsCount === 0) { return props.noMatchText || t("vs.select.noMatch"); } if (states.options.size === 0) { return props.noDataText || t("vs.select.noData"); } return null; }); watch( () => props.placeholder, (val) => { states.cachedPlaceHolder = states.currentPlaceholder = val; const hasValue = props.multiple && Array.isArray(props.modelValue) && props.modelValue.length > 0; if (hasValue) { states.currentPlaceholder = ""; } } ); watch( () => props.modelValue, (val) => { if (props.multiple) { if (val && val.length > 0 || input.value && states.query !== "") { states.currentPlaceholder = ""; } else { states.currentPlaceholder = states.cachedPlaceHolder; } if (props.filter) { states.query = ""; handleQueryChange(states.query); } nextTick(() => { if (reference.value && chips.value) { reference.value.style.height = `${chips.value.scrollHeight - 1}px`; } }); } setSelected(); nextTick(() => { var _a; (_a = popperRef.value) == null ? void 0 : _a.updatePopper(); }); }, { flush: "post", deep: true } ); watch( () => states.visible, (val) => { var _a, _b; if (!val) { input.value && input.value.blur(); handleQueryChange(""); states.query = ""; states.previousQuery = null; states.selectedLabel = ""; states.menuVisibleOnFocus = false; resetHoverIndex(); nextTick(() => { if (input.value && input.value.value === "" && selectedArray.value.length === 0) { states.currentPlaceholder = states.cachedPlaceHolder; } }); if (!props.multiple) { if (selectedArray.value.length) { if (props.filter && props.allowCreate && states.createdSelected && states.createdLabel) { states.selectedLabel = states.createdLabel; } else { states.selectedLabel = selectedArray.value[0].currentLabel; } if (props.filter) states.query = states.selectedLabel; } if (props.filter) { states.currentPlaceholder = states.cachedPlaceHolder; } } } else { (_a = popperRef.value) == null ? void 0 : _a.updatePopper(); if (props.filter) { states.filteredOptionsCount = states.optionsCount; states.query = states.selectedLabel; if (props.multiple) { (_b = input.value) == null ? void 0 : _b.focus(); } else { if (states.selectedLabel) { states.currentPlaceholder = `${states.selectedLabel}`; states.selectedLabel = ""; } } states.query && handleQueryChange(states.query); if (!props.multiple) { queryChange.value = ""; triggerRef(queryChange); } } } emit("visible-change", val); } ); watch( () => states.options.entries(), () => { var _a, _b; if (!isClient) return; (_a = popperRef.value) == null ? void 0 : _a.updatePopper(); const inputs = ((_b = selectWrapper.value) == null ? void 0 : _b.querySelectorAll("input")) || []; if (!Array.from(inputs).includes(document.activeElement)) { setSelected(); } if (props.defaultFirstOption && props.filter && states.filteredOptionsCount) { checkDefaultFirstOption(); } }, { flush: "post" } ); watch( () => states.hoverIndex, (val) => { if (val > -1) { hoverOption.value = optionsArray.value[val]; } else { hoverOption.value = void 0; } optionsArray.value.forEach((option) => { option.hover = isEqual(hoverOption.value, option); }); } ); const showTagList = computed(() => { if (!props.multiple) { return []; } return props.collapseChips ? selectedArray.value.slice(0, props.maxCollapseChips) : selectedArray.value; }); const collapseTagList = computed(() => { if (!props.multiple) { return []; } return props.collapseChips ? selectedArray.value.slice(props.maxCollapseChips) : []; }); const handleQueryChange = (val) => { if (states.previousQuery === val || states.isOnComposition) return; if (states.previousQuery === null && isFunction(props.filterMethod)) { states.previousQuery = val; return; } states.previousQuery = val; nextTick(() => { var _a; if (states.visible) (_a = popperRef.value) == null ? void 0 : _a.updatePopper(); }); states.hoverIndex = -1; if (props.multiple && props.filter) { nextTick(() => { managePlaceholder(); }); } if (isFunction(props.filterMethod)) { props.filterMethod(val); } else { states.filteredOptionsCount = states.optionsCount; queryChange.value = val; triggerRef(queryChange); } if (props.defaultFirstOption && props.filter && states.filteredOptionsCount) { nextTick(() => { checkDefaultFirstOption(); }); } }; const managePlaceholder = () => { var _a; if (states.currentPlaceholder !== "") { states.currentPlaceholder = ((_a = input.value) == null ? void 0 : _a.value) ? "" : states.cachedPlaceHolder; } }; const checkDefaultFirstOption = () => { const optionsInDropdown = optionsArray.value.filter( (n) => n.visible && !n.isDisabled && !n.groupDisabled ); const userCreatedOption = optionsInDropdown.find((n) => n.created); const firstOriginOption = optionsInDropdown[0]; states.hoverIndex = getValueIndex( optionsArray.value, firstOriginOption || userCreatedOption ); }; const setSelected = () => { states.selected.clear(); if (!props.multiple) { const option = getOption(props.modelValue); if (option.created) { states.createdLabel = `${option.value}`; states.createdSelected = true; } else { states.createdSelected = false; } states.selectedLabel = option.currentLabel; states.selected.set(option.value, option); if (props.filter) states.query = states.selectedLabel; return; } states.selectedLabel = ""; if (Array.isArray(props.modelValue)) { props.modelValue.forEach((value) => { const option = getOption(value); states.selected.set(option.value, option); }); } }; const getOption = (value) => { let option = null; for (let i = states.cachedOptions.size - 1; i >= 0; i--) { const cachedOption = cachedOptionsArray.value[i]; const isEqualValue = isObject(value) ? isEqual(cachedOption.value, value) : cachedOption.value === value; if (isEqualValue) { option = cachedOption; break; } } if (option) return option; const label = isObject(value) ? "" : !isNil(value) ? String(value) : ""; const newOption = { value, currentLabel: label, label, hit: true }; if (props.multiple) { newOption.hit = false; } return newOption; }; const resetHoverIndex = () => { setTimeout(() => { if (!props.multiple) { states.hoverIndex = optionsArray.value.findIndex((item) => { return isEqual(item, selectedArray.value[0]); }); return; } if (selectedArray.value.length > 0) { states.hoverIndex = Math.min.apply( null, selectedArray.value.map((selected) => { return optionsArray.value.findIndex((item) => { return isEqual(item.value, selected.value); }); }) ); return; } states.hoverIndex = -1; }, 300); }; const handleResize = () => { var _a; if (reference.value && chips.value) { reference.value.style.height = `${chips.value.scrollHeight}px`; } (_a = popperRef.value) == null ? void 0 : _a.updatePopper(); }; const onInputChange = () => { if (props.filter && states.query !== states.selectedLabel) { states.query = states.selectedLabel; handleQueryChange(states.selectedLabel || ""); } }; const debouncedOnInputChange = debounce(() => { onInputChange(); }, debounce$1.value); const debouncedQueryChange = debounce((e) => { handleQueryChange(e.target.value); }, debounce$1.value); const emitChange = (val) => { if (!isEqual(props.modelValue, val)) { emit("change", val); } }; const getLastNotDisabledIndex = (value) => findLastIndex( value, (it) => !states.disabledOptions.has(it) ); const deletePrevTag = (e) => { if (!props.multiple) return; if (e.code === EVENT_CODE.delete) return; const value = e.target.value; if (value.length <= 0 && !toggleLastOptionHitState()) { const value2 = props.modelValue.slice(); const lastNotDisabledIndex = getLastNotDisabledIndex(value2); if (lastNotDisabledIndex < 0) return; value2.splice(lastNotDisabledIndex, 1); emit(UPDATE_MODEL_EVENT, value2); emitChange(value2); } if (value.length === 1 && props.modelValue.length === 0) { states.currentPlaceholder = states.cachedPlaceHolder; } }; const deleteTag = (tag) => { const hasTag = states.cachedOptions.get(tag); if (!hasTag) return; const index = getValueIndex(selectedArray.value, hasTag); if (index > -1 && !selectDisabled.value) { const value = props.modelValue.slice(); value.splice(index, 1); emit(UPDATE_MODEL_EVENT, value); emitChange(value); emit("remove-tag", tag); } focus(); }; const deleteSelected = () => { const value = props.multiple ? [] : props.notValue; if (isArray(value)) { for (const item of selectedArray.value) { if (item.isDisabled) value.push(item.value); } } emit(UPDATE_MODEL_EVENT, value); emitChange(value); states.hoverIndex = -1; states.visible = false; emit("clear"); focus(); }; const handleOptionSelect = (option, byClick) => { var _a; if (props.multiple) { let modelValue = props.modelValue; if (!isArray(props.modelValue)) { if (states.options.has(modelValue)) { modelValue = [modelValue]; } else { modelValue = []; } } const value = modelValue.slice(); const optionIndex = getValueIndex(selectedArray.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) { states.query = ""; handleQueryChange(""); } if (props.filter) (_a = input.value) == null ? void 0 : _a.focus(); } else { emit(UPDATE_MODEL_EVENT, option.value); emitChange(option.value); states.visible = false; } states.isSilentBlur = byClick; setSoftFocus(); if (states.visible) return; nextTick(() => { scrollToOption(option); }); }; const getValueIndex = (arr, option) => { let index = -1; arr.some((item, i) => { if (isEqual(item.value, option.value)) { index = i; return true; } return false; }); return index; }; const setSoftFocus = () => { states.softFocus = true; const _input = input.value || reference.value; if (_input) { _input == null ? void 0 : _input.focus(); } }; const scrollToOption = (option) => { var _a, _b, _c, _d; let target; if (option == null ? void 0 : option.value) { const options = optionsArray.value.filter( (item) => item.value === option.value ); if (options.length > 0) { target = options[0].el; } } if (popperRef.value && target) { const menu = (_c = (_b = (_a = popperRef.value) == null ? void 0 : _a.contentRef) == null ? void 0 : _b.querySelector) == null ? void 0 : _c.call(_b, `.${ns.e("options")}`); if (menu) { setStyle(menu, "scroll-behavior", "smooth"); scrollIntoView(menu, target); removeStyle(menu, "scroll-behavior"); } } (_d = scrollbar.value) == null ? void 0 : _d.handleScroll(); }; const onOptionCreate = (value, option) => { states.optionsCount++; states.filteredOptionsCount++; states.options.set(value, option); states.cachedOptions.set(value, option); option.isDisabled && states.disabledOptions.set(value, option); }; const onOptionDestroy = (value, option) => { if (states.options.get(value) === option) { states.optionsCount--; states.filteredOptionsCount--; states.options.delete(value); } }; const resetInputState = (e) => { if (e.code !== EVENT_CODE.backspace) toggleLastOptionHitState(false); }; const toggleLastOptionHitState = (hit) => { if (!selectedArray.value.length) return; const lastNotDisabledIndex = getLastNotDisabledIndex( selectedArray.value.map((it) => it.value) ); const option = selectedArray.value[lastNotDisabledIndex]; if (!option) return; if (isBoolean(hit)) { option.hit = hit; return hit; } option.hit = !option.hit; return option.hit; }; const handleComposition = (event) => { var _a; const text = (_a = event.target) == null ? void 0 : _a.value; if (event.type === "compositionend") { states.isOnComposition = false; nextTick(() => handleQueryChange(text)); } else { const lastCharacter = text[text.length - 1] || ""; states.isOnComposition = !isKorean(lastCharacter); } }; const handleTarget = (target, condition = true) => { if (condition) states.targetOnElement = target; }; const handleMenuEnter = () => { nextTick(() => scrollToOption(selectedArray.value[0])); }; const focus = () => { var _a; states.visible = true; (_a = reference.value) == null ? void 0 : _a.focus(); }; const handleFocus = (event) => { if (!states.softFocus) { if (props.filter) { if (!states.visible) { states.menuVisibleOnFocus = true; } } emit("focus", event); } else { states.softFocus = false; } }; const blur = () => { var _a; states.visible = false; (_a = reference.value) == null ? void 0 : _a.blur(); }; const handleBlur = (event) => { nextTick(() => { if (states.isSilentBlur) { states.isSilentBlur = false; } else { emit("blur", event); } }); states.softFocus = false; }; const handleClearClick = () => { deleteSelected(); }; const showClearable = computed(() => { if (!props.clearable || props.disabled || props.loading) return false; if (optionsArray.value.length === 0) return false; const ignoreDisabledOptions = optionsArray.value.filter( (e) => e.isDisabled === false ); if (ignoreDisabledOptions.length === 0) return false; return states.mouseEnter; }); const handleClose = () => { states.visible = false; }; const handleKeydownEscape = (event) => { if (states.visible) { event.preventDefault(); event.stopPropagation(); states.visible = false; } }; const toggleMenu = (e) => { var _a; if (e && !states.mouseEnter) return; if (!selectDisabled.value) { if (states.menuVisibleOnFocus) { states.menuVisibleOnFocus = false; } else { if (!popperRef.value || !popperRef.value.isFocusInsideContent) { states.visible = !states.visible; } } if (states.visible) { ; (_a = input.value || reference.value) == null ? void 0 : _a.focus(); } } }; const selectOption = () => { if (!states.visible) { toggleMenu(); } else { if (optionsArray.value[states.hoverIndex]) { handleOptionSelect(optionsArray.value[states.hoverIndex], false); } } }; const optionsAllDisabled = computed( () => optionsArray.value.filter((option) => option.visible).every((option) => option.isDisabled) ); const navigateOptions = (direction = "next") => { if (!states.visible) { states.visible = true; return; } if (states.options.size === 0 || states.filteredOptionsCount === 0) return; if (states.isOnComposition) return; if (!optionsAllDisabled.value) { if (direction === "next") { states.hoverIndex++; if (states.hoverIndex === states.options.size) { states.hoverIndex = 0; } } else if (direction === "prev") { states.hoverIndex--; if (states.hoverIndex < 0) { states.hoverIndex = states.options.size - 1; } } const option = optionsArray.value[states.hoverIndex]; if (option.isDisabled === true || option.groupDisabled === true || !option.visible) { navigateOptions(direction); } nextTick( () => !isNil(hoverOption.value) && scrollToOption(hoverOption.value) ); } }; const handleMouseEnter = () => { states.mouseEnter = true; }; const handleMouseLeave = () => { states.mouseEnter = false; }; const processBeforeOpen = () => true; const processBeforeClose = () => { if (states.targetOnElement == null) return true; return !["chip-close", "input-filter"].includes(states.targetOnElement); }; return { showNewOption, inputId, optionsArray, cachedOptionsArray, selectedArray, handleResize, debouncedOnInputChange, debouncedQueryChange, deletePrevTag, deleteTag, deleteSelected, handleOptionSelect, scrollToOption, readonly, showClose, handleTarget, setSelected, managePlaceholder, selectDisabled, toggleLastOptionHitState, resetInputState, handleComposition, onOptionCreate, onOptionDestroy, handleMenuEnter, focus, handleFocus, blur, handleBlur, handleClearClick, showClearable, handleClose, handleKeydownEscape, toggleMenu, selectOption, navigateOptions, dropMenuVisible, queryChange, showTagList, collapseTagList, reference, input, popperRef, chips, selectWrapper, scrollbar, handleMouseEnter, handleMouseLeave, processBeforeOpen, processBeforeClose, emptyText, resetHoverIndex }; }; export { useSelect, useSelectStates }; //# sourceMappingURL=useSelect.mjs.map