2025-02-28 19:43:11 +08:00

394 lines
12 KiB
JavaScript

import { h, ref, toRef, computed, defineComponent, Transition, withDirectives, watchEffect } from 'vue';
import { createTreeMate } from 'treemate';
import { VBinder, VTarget, VFollower } from 'vueuc';
import { clickoutside } from 'vdirs';
import { useIsMounted, useMergedState } from 'vooks';
import { getPreciseEventTarget } from 'seemly';
import { createTmOptions } from "../../select/src/utils.mjs";
import { useFormItem, useTheme, useConfig, useThemeClass } from "../../_mixins/index.mjs";
import { call, useAdjustedTo, getFirstSlotVNode, warnOnce } from "../../_utils/index.mjs";
import { NInternalSelectMenu } from "../../_internal/index.mjs";
import { NInput } from "../../input/index.mjs";
import { autoCompleteLight } from "../styles/index.mjs";
import { mapAutoCompleteOptionsToSelectOptions } from "./utils.mjs";
import style from "./styles/index.cssr.mjs";
export const autoCompleteProps = Object.assign(Object.assign({}, useTheme.props), {
to: useAdjustedTo.propTo,
menuProps: Object,
bordered: {
type: Boolean,
default: undefined
},
clearable: {
type: Boolean,
default: undefined
},
defaultValue: {
type: String,
default: null
},
loading: {
type: Boolean,
default: undefined
},
disabled: {
type: Boolean,
default: undefined
},
placeholder: String,
placement: {
type: String,
default: 'bottom-start'
},
value: String,
blurAfterSelect: Boolean,
clearAfterSelect: Boolean,
getShow: Function,
showEmpty: Boolean,
inputProps: Object,
renderOption: Function,
renderLabel: Function,
size: String,
options: {
type: Array,
default: () => []
},
zIndex: Number,
status: String,
'onUpdate:value': [Function, Array],
onUpdateValue: [Function, Array],
onSelect: [Function, Array],
onBlur: [Function, Array],
onFocus: [Function, Array],
// deprecated
onInput: [Function, Array]
});
export default defineComponent({
name: 'AutoComplete',
props: autoCompleteProps,
setup(props) {
if (process.env.NODE_ENV !== 'production') {
watchEffect(() => {
if (props.onInput !== undefined) {
warnOnce('auto-complete', '`on-input` is deprecated, please use `on-update:value` instead.');
}
});
}
const {
mergedBorderedRef,
namespaceRef,
mergedClsPrefixRef,
inlineThemeDisabled
} = useConfig(props);
const formItem = useFormItem(props);
const {
mergedSizeRef,
mergedDisabledRef,
mergedStatusRef
} = formItem;
const triggerElRef = ref(null);
const menuInstRef = ref(null);
const uncontrolledValueRef = ref(props.defaultValue);
const controlledValueRef = toRef(props, 'value');
const mergedValueRef = useMergedState(controlledValueRef, uncontrolledValueRef);
const canBeActivatedRef = ref(false);
const isComposingRef = ref(false);
const themeRef = useTheme('AutoComplete', '-auto-complete', style, autoCompleteLight, props, mergedClsPrefixRef);
const selectOptionsRef = computed(() => {
return mapAutoCompleteOptionsToSelectOptions(props.options);
});
const mergedShowOptionsRef = computed(() => {
const {
getShow
} = props;
if (getShow) {
return getShow(mergedValueRef.value || '');
}
return !!mergedValueRef.value;
});
const activeRef = computed(() => {
return mergedShowOptionsRef.value && canBeActivatedRef.value && (props.showEmpty ? true : !!selectOptionsRef.value.length);
});
const treeMateRef = computed(() => createTreeMate(selectOptionsRef.value, createTmOptions('value', 'children')));
function doUpdateValue(value) {
const {
'onUpdate:value': _onUpdateValue,
onUpdateValue,
onInput
} = props;
const {
nTriggerFormInput,
nTriggerFormChange
} = formItem;
if (onUpdateValue) call(onUpdateValue, value);
if (_onUpdateValue) call(_onUpdateValue, value);
if (onInput) call(onInput, value);
uncontrolledValueRef.value = value;
nTriggerFormInput();
nTriggerFormChange();
}
function doSelect(value) {
const {
onSelect
} = props;
const {
nTriggerFormInput,
nTriggerFormChange
} = formItem;
if (onSelect) call(onSelect, value);
nTriggerFormInput();
nTriggerFormChange();
}
function doBlur(e) {
const {
onBlur
} = props;
const {
nTriggerFormBlur
} = formItem;
if (onBlur) call(onBlur, e);
nTriggerFormBlur();
}
function doFocus(e) {
const {
onFocus
} = props;
const {
nTriggerFormFocus
} = formItem;
if (onFocus) call(onFocus, e);
nTriggerFormFocus();
}
function handleCompositionStart() {
isComposingRef.value = true;
}
function handleCompositionEnd() {
window.setTimeout(() => {
isComposingRef.value = false;
}, 0);
}
function handleKeyDown(e) {
var _a, _b, _c;
switch (e.key) {
case 'Enter':
if (!isComposingRef.value) {
const pendingOptionTmNode = (_a = menuInstRef.value) === null || _a === void 0 ? void 0 : _a.getPendingTmNode();
if (pendingOptionTmNode) {
select(pendingOptionTmNode.rawNode);
e.preventDefault();
}
}
break;
case 'ArrowDown':
(_b = menuInstRef.value) === null || _b === void 0 ? void 0 : _b.next();
break;
case 'ArrowUp':
(_c = menuInstRef.value) === null || _c === void 0 ? void 0 : _c.prev();
break;
}
}
function select(option) {
if ((option === null || option === void 0 ? void 0 : option.value) !== undefined) {
doSelect(option.value);
if (props.clearAfterSelect) {
doUpdateValue(null);
} else if (option.label !== undefined) {
doUpdateValue(option.label);
}
canBeActivatedRef.value = false;
if (props.blurAfterSelect) {
blur();
}
}
}
function handleClear() {
doUpdateValue(null);
}
function handleFocus(e) {
canBeActivatedRef.value = true;
doFocus(e);
}
function handleBlur(e) {
canBeActivatedRef.value = false;
doBlur(e);
}
function handleInput(value) {
canBeActivatedRef.value = true;
doUpdateValue(value);
}
function handleToggle(option) {
select(option.rawNode);
}
function handleClickOutsideMenu(e) {
var _a;
if (!((_a = triggerElRef.value) === null || _a === void 0 ? void 0 : _a.contains(getPreciseEventTarget(e)))) {
canBeActivatedRef.value = false;
}
}
function blur() {
var _a, _b;
if ((_a = triggerElRef.value) === null || _a === void 0 ? void 0 : _a.contains(document.activeElement)) {
;
(_b = document.activeElement) === null || _b === void 0 ? void 0 : _b.blur();
}
}
const cssVarsRef = computed(() => {
const {
common: {
cubicBezierEaseInOut
},
self: {
menuBoxShadow
}
} = themeRef.value;
return {
'--n-menu-box-shadow': menuBoxShadow,
'--n-bezier': cubicBezierEaseInOut
};
});
const themeClassHandle = inlineThemeDisabled ? useThemeClass('auto-complete', undefined, cssVarsRef, props) : undefined;
const inputInstRef = ref(null);
const exposedMethods = {
focus: () => {
var _a;
(_a = inputInstRef.value) === null || _a === void 0 ? void 0 : _a.focus();
},
blur: () => {
var _a;
(_a = inputInstRef.value) === null || _a === void 0 ? void 0 : _a.blur();
}
};
return {
focus: exposedMethods.focus,
blur: exposedMethods.blur,
inputInstRef,
uncontrolledValue: uncontrolledValueRef,
mergedValue: mergedValueRef,
isMounted: useIsMounted(),
adjustedTo: useAdjustedTo(props),
menuInstRef,
triggerElRef,
treeMate: treeMateRef,
mergedSize: mergedSizeRef,
mergedDisabled: mergedDisabledRef,
active: activeRef,
mergedStatus: mergedStatusRef,
handleClear,
handleFocus,
handleBlur,
handleInput,
handleToggle,
handleClickOutsideMenu,
handleCompositionStart,
handleCompositionEnd,
handleKeyDown,
mergedTheme: themeRef,
cssVars: inlineThemeDisabled ? undefined : cssVarsRef,
themeClass: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass,
onRender: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender,
mergedBordered: mergedBorderedRef,
namespace: namespaceRef,
mergedClsPrefix: mergedClsPrefixRef
};
},
render() {
const {
mergedClsPrefix
} = this;
return h("div", {
class: `${mergedClsPrefix}-auto-complete`,
ref: "triggerElRef",
onKeydown: this.handleKeyDown,
onCompositionstart: this.handleCompositionStart,
onCompositionend: this.handleCompositionEnd
}, h(VBinder, null, {
default: () => [h(VTarget, null, {
default: () => {
const defaultSlot = this.$slots.default;
if (defaultSlot) {
return getFirstSlotVNode(this.$slots, 'default', {
handleInput: this.handleInput,
handleFocus: this.handleFocus,
handleBlur: this.handleBlur,
value: this.mergedValue
});
}
const {
mergedTheme
} = this;
return h(NInput, {
ref: "inputInstRef",
status: this.mergedStatus,
theme: mergedTheme.peers.Input,
themeOverrides: mergedTheme.peerOverrides.Input,
bordered: this.mergedBordered,
value: this.mergedValue,
placeholder: this.placeholder,
size: this.mergedSize,
disabled: this.mergedDisabled,
clearable: this.clearable,
loading: this.loading,
inputProps: this.inputProps,
onClear: this.handleClear,
onFocus: this.handleFocus,
onUpdateValue: this.handleInput,
onBlur: this.handleBlur
}, {
suffix: () => {
var _a, _b;
return (_b = (_a = this.$slots).suffix) === null || _b === void 0 ? void 0 : _b.call(_a);
},
prefix: () => {
var _a, _b;
return (_b = (_a = this.$slots).prefix) === null || _b === void 0 ? void 0 : _b.call(_a);
}
});
}
}), h(VFollower, {
show: this.active,
to: this.adjustedTo,
containerClass: this.namespace,
zIndex: this.zIndex,
teleportDisabled: this.adjustedTo === useAdjustedTo.tdkey,
placement: this.placement,
width: "target"
}, {
default: () => h(Transition, {
name: "fade-in-scale-up-transition",
appear: this.isMounted
}, {
default: () => {
var _a;
(_a = this.onRender) === null || _a === void 0 ? void 0 : _a.call(this);
if (!this.active) return null;
const {
menuProps
} = this;
return withDirectives(h(NInternalSelectMenu, Object.assign({}, menuProps, {
clsPrefix: mergedClsPrefix,
ref: "menuInstRef",
theme: this.mergedTheme.peers.InternalSelectMenu,
themeOverrides: this.mergedTheme.peerOverrides.InternalSelectMenu,
"auto-pending": true,
class: [`${mergedClsPrefix}-auto-complete-menu`, this.themeClass, menuProps === null || menuProps === void 0 ? void 0 : menuProps.class],
style: [menuProps === null || menuProps === void 0 ? void 0 : menuProps.style, this.cssVars],
treeMate: this.treeMate,
multiple: false,
renderLabel: this.renderLabel,
renderOption: this.renderOption,
size: "medium",
onToggle: this.handleToggle
}), {
empty: () => {
var _a, _b;
return (_b = (_a = this.$slots).empty) === null || _b === void 0 ? void 0 : _b.call(_a);
}
}), [[clickoutside, this.handleClickOutsideMenu, undefined, {
capture: true
}]]);
}
})
})]
}));
}
});