@fesjs/fes-design
Version:
fes-design for PC
434 lines (425 loc) • 16.1 kB
JavaScript
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 };