import { ref, SetupContext, toRef, reactive, Ref, watch, onMounted } from 'vue';
import { cloneDeep } from 'lodash';
import { initActiveIndexs, initSingleIptValue } from './use-cascader-single';
import { initMultipleCascaderItem, initTagList, getMultiModelValues } from './use-cascader-multiple';
import type { CascaderItem, CascaderValueType, CascaderProps, UseCascaderFn } from '../src/cascader-types';
import { popupHandles } from './use-cascader-popup';
import { useCascaderItem } from './use-cascader-item';
import { useRootStyle } from './use-cascader-style';
import { useRootClassName } from './use-cascader-class';
import { useFilter } from './use-filter';
export const useCascader = (props: CascaderProps, ctx: SetupContext): UseCascaderFn => {
const origin = ref<HTMLElement>();
const overlayRef = ref<HTMLElement>();
const cascaderOptions = reactive<[CascaderItem[]]>(cloneDeep([props?.options]));
const multiple = toRef(props, 'multiple');
const inputValue = ref('');
const tagList = ref<CascaderItem[]>([]);
const rootStyle = useRootStyle(props);
const showClearable = ref(false);
const position = ref(['bottom-start', 'top-start']);
let initIptValue = props.modelValue.length > 0 ? true : false;
const { menuShow, menuOpenClass, openPopup, stopDefault, updateStopDefaultType } = popupHandles(props, overlayRef, origin);
const rootClasses = useRootClassName(props, menuShow);
const { cascaderItemNeedProps } = useCascaderItem(props, stopDefault, tagList.value);
const getInputValue = (label: string, arr: CascaderItem[], inputValueCache?: Ref<string>, showPath?: boolean) => {
if (inputValueCache !== undefined) {
if (!showPath) {
inputValueCache.value = label;
} else {
inputValueCache.value += label + (arr?.length > 0 ? ' / ' : '');
}
}
};
* 控制视图更新
* 注意视图更新不区分单选或者多选
* @param activeIndexs 视图展示下标集合
* @param currentOption 选中的某项
* @param index value的下标,起始为0
*/
const updateCascaderView = (value: CascaderValueType, currentOption: CascaderItem[], index: number) => {
if (index === value.length) {
return;
}
const i = value[index] as number;
const current = currentOption[i];
const children = current?.children || [];
if (children?.length > 0) {
cascaderOptions[index + 1] = children;
updateCascaderView(value, children, index + 1);
} else {
cascaderOptions.splice(index + 1, cascaderOptions.length - 1);
}
};
* 根据value筛选每列中选中item
*/
const getCurrentOption = (currentOption: CascaderItem[], i: number) => {
return currentOption.filter((item) => item?.value === i)[0];
};
* 选中项输出
* 需要区分单选或多选模式
* @param value 选中值集合
* @param currentOption 激活的某项
* @param index value的下标,起始为0
*/
const updateCascaderValue = (value: CascaderValueType, currentOption: CascaderItem[], index: number) => {
if (!multiple.value) {
if (index === value.length) {
return;
}
const i = value[index] as number;
const current = getCurrentOption(currentOption, i);
const children: CascaderItem[] = current?.children || [];
getInputValue(current.label, children, cascaderItemNeedProps.inputValueCache, props.showPath);
if (children?.length > 0) {
updateCascaderValue(value, children, index + 1);
}
} else {
const rootColumn = cascaderOptions[0] || [];
value.forEach((targetValue) => {
initMultipleCascaderItem(targetValue, rootColumn, tagList.value);
});
}
};
* 监听视图更新
*/
watch(cascaderItemNeedProps.activeIndexs as CascaderValueType, (val) => {
cascaderOptions.splice(val?.length || 0, cascaderOptions.length - 1);
updateCascaderView(val, cascaderOptions[0], 0);
});
* 监听点击最终的节点输出内容
*/
watch(
() => cascaderItemNeedProps.confirmInputValueFlg?.value,
() => {
multiple.value ? initTagList(tagList.value) : initSingleIptValue(cascaderItemNeedProps.inputValueCache);
cascaderItemNeedProps.value = reactive(cloneDeep(cascaderItemNeedProps.valueCache as CascaderValueType));
menuShow.value = false;
updateStopDefaultType();
updateCascaderValue(cascaderItemNeedProps.value as CascaderValueType, cascaderOptions[0], 0);
inputValue.value = cascaderItemNeedProps.inputValueCache?.value as string;
if (initIptValue && !multiple.value) {
initActiveIndexs(props.modelValue, cascaderOptions[0], 0, cascaderItemNeedProps.activeIndexs as number[]);
initIptValue = false;
}
ctx.emit('update:modelValue', cascaderItemNeedProps.value);
},
{
immediate: true,
}
);
watch(
() => tagList,
() => {
if (multiple.value) {
ctx.emit('update:modelValue', getMultiModelValues(tagList.value));
}
},
{
immediate: true,
deep: true,
}
);
const showClear = () => {
if (props.disabled) {
return;
}
showClearable.value = true;
};
const hideClear = () => {
showClearable.value = false;
};
const clearData = (e: MouseEvent) => {
e.stopPropagation();
menuShow.value = false;
inputValue.value = '';
ctx.emit('update:modelValue', []);
menuShow.value = false;
cascaderOptions.splice(1, cascaderOptions.length - 1);
if (cascaderItemNeedProps.inputValueCache) {
cascaderItemNeedProps.inputValueCache.value = '';
}
cascaderItemNeedProps.valueCache?.splice(0);
};
watch(
() => props.modelValue,
(newVal) => {
ctx.emit('change', newVal);
},
{ immediate: true, deep: true }
);
watch(
() => props.options,
() => {
const len = cascaderOptions.length;
cascaderOptions.splice(0, len, ...cloneDeep([props.options]));
},
{ deep: true }
);
const onFocus = (e: FocusEvent) => {
ctx.emit('focus', e);
};
const onBlur = (e: FocusEvent) => {
ctx.emit('blur', e);
};
const { handleInput, suggestionsList, isSearching, chooseSuggestion } = useFilter(
props,
ctx,
menuShow,
cascaderItemNeedProps,
updateCascaderView,
inputValue,
cascaderOptions
);
onMounted(() => {
origin.value?.addEventListener('click', openPopup);
});
return {
origin,
overlayRef,
menuShow,
cascaderItemNeedProps,
rootClasses,
menuOpenClass,
inputValue,
openPopup,
rootStyle,
showClearable,
position,
cascaderOptions,
tagList,
showClear,
hideClear,
clearData,
handleInput,
multiple,
suggestionsList,
isSearching,
chooseSuggestion,
onFocus,
onBlur,
};
};