UNPKG

@fesjs/fes-design

Version:
434 lines (425 loc) 16.1 kB
import _defineProperty from '@babel/runtime/helpers/esm/defineProperty'; import { defineComponent, computed, ref, watch, unref, resolveComponent, openBlock, createElementBlock, normalizeClass, createVNode, withCtx, normalizeStyle, withDirectives, withModifiers, vShow } from 'vue'; import { isArray, debounce } from 'lodash-es'; import getPrefixCls from '../_util/getPrefixCls'; import { useTheme } from '../_theme/useTheme'; import { useArrayModel, useNormalModel } from '../_util/use/useModel'; import { UPDATE_MODEL_EVENT, CHANGE_EVENT } from '../_util/constants'; import useFormAdaptor from '../_util/use/useFormAdaptor'; import Popper from '../popper'; import SelectTrigger from '../select-trigger'; import Cascader from '../cascader/cascader'; import { selectProps } from '../select/props'; import { cascaderProps } from '../cascader/props'; import { getCascadeChildrenByKeys, getCascadeParentByKeys, handleParent, handleChildren } from '../cascader/helper'; import { useLocale } from '../config-provider/useLocale'; import { CHECK_STRATEGY } from '../cascader/const'; import OptionList from '../select/optionList'; import { prefixCls as prefixCls$1 } from '../select/const'; import { getKeysByCurrentValue, getExpandedKeysBySelectedKeys, getNotMatchedPathByKey, getCurrentValueByKeys } from './helper'; import { SELECT_CASCADER_NAME, LABEL_SEPARATOR } from './const'; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } const prefixCls = getPrefixCls('select-cascader'); const selectCascaderProps = _objectSpread(_objectSpread(_objectSpread({}, selectProps), cascaderProps), {}, { modelValue: { type: [String, Number, Array] } }); var script = defineComponent({ name: SELECT_CASCADER_NAME, components: { Popper, SelectTrigger, Cascader, OptionList }, props: selectCascaderProps, emits: [UPDATE_MODEL_EVENT, CHANGE_EVENT, 'update:expandedKeys', 'removeTag', 'visibleChange', 'focus', 'blur', 'clear'], setup(props, _ref) { let { emit, attrs } = _ref; useTheme(); const { validate, isError, isFormDisabled } = useFormAdaptor({ valueType: computed(() => props.multiple ? 'array' : 'string') }); const isOpened = ref(false); // 与 props 中 modelValue 类型保持一致 const [currentValue, updateCurrentValue] = props.multiple ? useArrayModel(props, emit) : useNormalModel(props, emit); const innerDisabled = computed(() => props.disabled || isFormDisabled.value); const cascaderRef = ref(); const triggerDomRef = ref(); const triggerWidth = ref(0); const innerFilterable = computed(() => { if (props.filterable && props.remote) { console.warn(`[${SELECT_CASCADER_NAME}]: remote 被设定时, filterable 不生效`); } return props.filterable && !props.remote && !innerDisabled.value; }); const filterText = ref(''); const filteredOptions = ref([]); const { t } = useLocale(); const inputPlaceholder = computed(() => props.placeholder || t('select.placeholder')); const listEmptyText = computed(() => props.emptyText || t('select.emptyText')); const filterEmptyText = computed(() => t('select.filterEmptyText')); watch(isOpened, () => { emit('visibleChange', unref(isOpened)); // trigger 在 mounted 之后可能会改变 if (isOpened.value && triggerDomRef.value) { triggerWidth.value = triggerDomRef.value.$el.offsetWidth; } }); const handleChange = () => { emit(CHANGE_EVENT, currentValue.value); validate(CHANGE_EVENT); }; const nodeList = ref({}); const onChangeNodeList = data => { nodeList.value = data; }; // 为避免过滤项之间选中操作相互干扰,这里仅过滤叶子节点 const filterNodeList = computed(() => { const list = []; if (innerFilterable.value) { Object.keys(nodeList.value).forEach(key => { if (!nodeList.value[key].hasChildren) { list.push(nodeList.value[key]); } }); } return list; }); const cascaderSelectable = computed(() => !props.multiple); const cascaderCheckable = computed(() => props.multiple); const selectedKeys = computed(() => { if (!props.multiple) { return getKeysByCurrentValue(currentValue.value, props); } return []; }); const expandedKeys = computed({ get: () => { if (!props.multiple) { return getExpandedKeysBySelectedKeys(nodeList.value, selectedKeys.value); } return []; }, set: nextValue => { emit('update:expandedKeys', nextValue); } }); const checkedKeys = computed(() => { if (props.multiple) { const keys = getKeysByCurrentValue(currentValue.value, props); if (!props.cascade) { return keys; } if (props.checkStrictly === CHECK_STRATEGY.ALL) { return keys; } if (props.checkStrictly === CHECK_STRATEGY.PARENT) { return getCascadeChildrenByKeys(nodeList.value, keys); } if (props.checkStrictly === CHECK_STRATEGY.CHILD) { return getCascadeParentByKeys(nodeList.value, keys); } } return []; }); const initLoadKeys = computed(() => { let keys = []; if (!(props.remote && props.loadData)) { return keys; } if (!props.emitPath) { return keys; } if (!isArray(currentValue.value)) { return keys; } currentValue.value.forEach(value => keys = keys.concat(value)); keys = Array.from(new Set(keys)); // 去重处理 return keys; }); watch(() => props.checkStrictly, () => { if (props.multiple && props.cascade) { updateCurrentValue([]); handleChange(); } }); watch([() => props.emitPath, () => props.cascade], () => { const value = props.multiple || props.emitPath ? [] : null; updateCurrentValue(value); handleChange(); }); const handleClear = () => { const value = props.multiple || props.emitPath ? [] : null; if (props.multiple ? checkedKeys.value.length : selectedKeys.value.length) { updateCurrentValue(value); handleChange(); } filterText.value = ''; emit('clear'); }; const handleSelect = data => { if (innerDisabled.value) { return; } if (!props.multiple) { isOpened.value = false; } updateCurrentValue(getCurrentValueByKeys(nodeList.value, data.selectedKeys, props)); handleChange(); }; const handleCheck = data => { if (innerDisabled.value) { return; } if (!props.multiple) { isOpened.value = false; } updateCurrentValue(getCurrentValueByKeys(nodeList.value, data.checkedKeys, props)); handleChange(); }; const handleRemove = value => { if (!props.multiple) { return; } const values = getKeysByCurrentValue(currentValue.value, props); const findIndex = values.indexOf(value); if (findIndex !== -1) { emit('removeTag', value); // 兼容关联场景 if (!props.cascade) { values.splice(findIndex, 1); } else { const { isLeaf, children, indexPath } = nodeList.value[value]; values.splice(findIndex, 1); handleParent(values, indexPath, false, nodeList.value); if (!isLeaf) { handleChildren(values, children, false); } } updateCurrentValue(getCurrentValueByKeys(nodeList.value, values, props)); handleChange(); } }; const selectedOptions = computed(() => { const values = getKeysByCurrentValue(currentValue.value, props); // 兼容异步加载,未匹配到节点的情况 return values.filter(Boolean).map(curValue => { const { value, label, path } = nodeList.value[curValue] || { value: curValue, label: curValue, path: getNotMatchedPathByKey(currentValue.value, props, curValue) }; const formatLabel = props.showPath ? path.map(item => `${item.label}`).join(LABEL_SEPARATOR) : label; return { value, label: formatLabel, path }; }); }); const focus = e => { emit('focus', e); validate('focus'); }; const blur = e => { if (isOpened.value) { isOpened.value = false; } filterText.value = ''; emit('blur', e); validate('blur'); }; const handleFilterTextChange = val => { filterText.value = val; }; watch(filterText, debounce(() => { const currentNodeList = []; if (innerFilterable.value && filterText.value) { filterNodeList.value.forEach(node => { let isFilter = false; const labelList = node.path.map(item => item.label); if (props.filter) { isFilter = props.filter(filterText.value, node); } else { isFilter = labelList.some(label => String(label).includes(filterText.value)); } if (isFilter) { currentNodeList.push({ value: node.value, label: labelList.join(LABEL_SEPARATOR), disabled: node.disabled }); } }); } filteredOptions.value = currentNodeList; }, 300)); const filterIsSelect = value => { const optVal = unref(value); if (cascaderSelectable.value) { return selectedKeys.value.includes(optVal); } if (cascaderCheckable.value) { return checkedKeys.value.includes(optVal); } }; const filterDropdownStyle = computed(() => { const style = {}; if (triggerWidth.value) { style['min-width'] = `${triggerWidth.value}px`; } return style; }); const handleFilterSelect = value => { if (cascaderSelectable.value) { var _cascaderRef$value; (_cascaderRef$value = cascaderRef.value) === null || _cascaderRef$value === void 0 || _cascaderRef$value.selectNode(value); filterText.value = ''; } if (cascaderCheckable.value) { var _cascaderRef$value2; (_cascaderRef$value2 = cascaderRef.value) === null || _cascaderRef$value2 === void 0 || _cascaderRef$value2.checkNode(value); } }; return { triggerDomRef, cascaderRef, prefixCls, selectPrefixCls: prefixCls$1, isOpened, currentValue, handleRemove, handleClear, selectedOptions, focus, blur, handleFilterTextChange, cascaderSelectable, selectedKeys, expandedKeys, cascaderCheckable, handleSelect, handleCheck, checkedKeys, onChangeNodeList, inputPlaceholder, isError, initLoadKeys, attrs, innerDisabled, innerFilterable, listEmptyText, filterEmptyText, filterText, filteredOptions, filterIsSelect, filterDropdownStyle, handleFilterSelect }; } }); function render(_ctx, _cache, $props, $setup, $data, $options) { const _component_SelectTrigger = resolveComponent("SelectTrigger"); const _component_Cascader = resolveComponent("Cascader"); const _component_OptionList = resolveComponent("OptionList"); const _component_Popper = resolveComponent("Popper"); return openBlock(), createElementBlock("div", { class: normalizeClass(_ctx.prefixCls) }, [createVNode(_component_Popper, { modelValue: _ctx.isOpened, "onUpdate:modelValue": _cache[3] || (_cache[3] = $event => _ctx.isOpened = $event), trigger: "click", placement: "bottom-start", popperClass: `${_ctx.prefixCls}-popper`, appendToContainer: _ctx.appendToContainer, getContainer: _ctx.getContainer, offset: 4, hideAfter: 0, disabled: _ctx.innerDisabled, lazy: false }, { trigger: withCtx(() => [createVNode(_component_SelectTrigger, { ref: "triggerDomRef", selectedOptions: _ctx.selectedOptions, disabled: _ctx.innerDisabled, clearable: _ctx.clearable, isOpened: _ctx.isOpened, multiple: _ctx.multiple, placeholder: _ctx.inputPlaceholder, filterable: _ctx.innerFilterable, collapseTags: _ctx.collapseTags, collapseTagsLimit: _ctx.collapseTagsLimit, tagBordered: _ctx.tagBordered, class: normalizeClass([{ 'is-error': _ctx.isError }, _ctx.attrs.class]), style: normalizeStyle(_ctx.attrs.style), renderTag: _ctx.$slots.tag, onRemove: _ctx.handleRemove, onClear: _ctx.handleClear, onFocus: _ctx.focus, onBlur: _ctx.blur, onInput: _ctx.handleFilterTextChange }, null, 8 /* PROPS */, ["selectedOptions", "disabled", "clearable", "isOpened", "multiple", "placeholder", "filterable", "collapseTags", "collapseTagsLimit", "tagBordered", "class", "style", "renderTag", "onRemove", "onClear", "onFocus", "onBlur", "onInput"])]), default: withCtx(() => [withDirectives(createVNode(_component_Cascader, { ref: "cascaderRef", expandedKeys: _ctx.expandedKeys, "onUpdate:expandedKeys": _cache[0] || (_cache[0] = $event => _ctx.expandedKeys = $event), selectedKeys: _ctx.selectedKeys, checkedKeys: _ctx.checkedKeys, initLoadKeys: _ctx.initLoadKeys, data: _ctx.data, emptyText: _ctx.listEmptyText, selectable: _ctx.cascaderSelectable, checkable: _ctx.cascaderCheckable, checkStrictly: _ctx.checkStrictly, cascade: _ctx.cascade, multiple: _ctx.multiple, childrenField: _ctx.childrenField, valueField: _ctx.valueField, labelField: _ctx.labelField, remote: _ctx.remote, loadData: _ctx.loadData, expandTrigger: _ctx.expandTrigger, isOpened: _ctx.isOpened, "onUpdate:nodeList": _ctx.onChangeNodeList, onSelect: _ctx.handleSelect, onCheck: _ctx.handleCheck, onMousedown: _cache[1] || (_cache[1] = withModifiers(() => {}, ["prevent"])) }, null, 8 /* PROPS */, ["expandedKeys", "selectedKeys", "checkedKeys", "initLoadKeys", "data", "emptyText", "selectable", "checkable", "checkStrictly", "cascade", "multiple", "childrenField", "valueField", "labelField", "remote", "loadData", "expandTrigger", "isOpened", "onUpdate:nodeList", "onSelect", "onCheck"]), [[vShow, !_ctx.filterText]]), withDirectives(createVNode(_component_OptionList, { options: _ctx.filteredOptions, prefixCls: _ctx.selectPrefixCls, containerStyle: _ctx.filterDropdownStyle, isSelect: _ctx.filterIsSelect, onSelect: _ctx.handleFilterSelect, emptyText: _ctx.filterEmptyText, filterText: _ctx.filterText, filterTextHighlight: _ctx.filterTextHighlight, onMousedown: _cache[2] || (_cache[2] = withModifiers(() => {}, ["prevent"])) }, null, 8 /* PROPS */, ["options", "prefixCls", "containerStyle", "isSelect", "onSelect", "emptyText", "filterText", "filterTextHighlight"]), [[vShow, _ctx.filterText]])]), _: 1 /* STABLE */ }, 8 /* PROPS */, ["modelValue", "popperClass", "appendToContainer", "getContainer", "disabled"])], 2 /* CLASS */); } script.render = render; script.__file = "components/select-cascader/selectCascader.vue"; export { script as default, selectCascaderProps };