ant-design-vue
Version:
An enterprise-class UI design language and Vue-based implementation
311 lines • 9.64 kB
JavaScript
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
import _extends from "@babel/runtime/helpers/esm/extends";
import { resolveDirective as _resolveDirective, createVNode as _createVNode } from "vue";
var __rest = this && this.__rest || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
};
import { toRef, watchEffect, defineComponent, provide, ref, reactive, onUpdated, nextTick, computed } from 'vue';
import classNames from '../../_util/classNames';
import KeyCode from '../../_util/KeyCode';
import { initDefaultProps } from '../../_util/props-util';
import { getBeforeSelectionText, getLastMeasureIndex, replaceWithMeasure, setInputSelection } from './util';
import KeywordTrigger from './KeywordTrigger';
import { vcMentionsProps, defaultProps } from './mentionsProps';
import MentionsContextKey from './MentionsContext';
import omit from '../../_util/omit';
import BaseInput from '../../_util/BaseInput';
function noop() {}
export default defineComponent({
compatConfig: {
MODE: 3
},
name: 'Mentions',
inheritAttrs: false,
props: initDefaultProps(vcMentionsProps, defaultProps),
emits: ['change', 'select', 'search', 'focus', 'blur', 'pressenter'],
setup(props, _ref) {
let {
emit,
attrs,
expose,
slots
} = _ref;
const measure = ref(null);
const textarea = ref(null);
const focusId = ref();
const state = reactive({
value: props.value || '',
measuring: false,
measureLocation: 0,
measureText: null,
measurePrefix: '',
activeIndex: 0,
isFocus: false
});
watchEffect(() => {
state.value = props.value;
});
const triggerChange = val => {
emit('change', val);
};
const onChange = _ref2 => {
let {
target: {
value
}
} = _ref2;
triggerChange(value);
};
const startMeasure = (measureText, measurePrefix, measureLocation) => {
_extends(state, {
measuring: true,
measureText,
measurePrefix,
measureLocation,
activeIndex: 0
});
};
const stopMeasure = callback => {
_extends(state, {
measuring: false,
measureLocation: 0,
measureText: null
});
callback === null || callback === void 0 ? void 0 : callback();
};
const onKeyDown = event => {
const {
which
} = event;
// Skip if not measuring
if (!state.measuring) {
return;
}
if (which === KeyCode.UP || which === KeyCode.DOWN) {
// Control arrow function
const optionLen = options.value.length;
const offset = which === KeyCode.UP ? -1 : 1;
const newActiveIndex = (state.activeIndex + offset + optionLen) % optionLen;
state.activeIndex = newActiveIndex;
event.preventDefault();
} else if (which === KeyCode.ESC) {
stopMeasure();
} else if (which === KeyCode.ENTER) {
// Measure hit
event.preventDefault();
if (!options.value.length) {
stopMeasure();
return;
}
const option = options.value[state.activeIndex];
selectOption(option);
}
};
const onKeyUp = event => {
const {
key,
which
} = event;
const {
measureText: prevMeasureText,
measuring
} = state;
const {
prefix,
validateSearch
} = props;
const target = event.target;
if (target.composing) {
return;
}
const selectionStartText = getBeforeSelectionText(target);
const {
location: measureIndex,
prefix: measurePrefix
} = getLastMeasureIndex(selectionStartText, prefix);
// Skip if match the white key list
if ([KeyCode.ESC, KeyCode.UP, KeyCode.DOWN, KeyCode.ENTER].indexOf(which) !== -1) {
return;
}
if (measureIndex !== -1) {
const measureText = selectionStartText.slice(measureIndex + measurePrefix.length);
const validateMeasure = validateSearch(measureText, props);
const matchOption = !!getOptions(measureText).length;
if (validateMeasure) {
if (key === measurePrefix || key === 'Shift' || measuring || measureText !== prevMeasureText && matchOption) {
startMeasure(measureText, measurePrefix, measureIndex);
}
} else if (measuring) {
// Stop if measureText is invalidate
stopMeasure();
}
/**
* We will trigger `onSearch` to developer since they may use for async update.
* If met `space` means user finished searching.
*/
if (validateMeasure) {
emit('search', measureText, measurePrefix);
}
} else if (measuring) {
stopMeasure();
}
};
const onPressEnter = event => {
if (!state.measuring) {
emit('pressenter', event);
}
};
const onInputFocus = event => {
onFocus(event);
};
const onInputBlur = event => {
onBlur(event);
};
const onFocus = event => {
clearTimeout(focusId.value);
const {
isFocus
} = state;
if (!isFocus && event) {
emit('focus', event);
}
state.isFocus = true;
};
const onBlur = event => {
focusId.value = setTimeout(() => {
state.isFocus = false;
stopMeasure();
emit('blur', event);
}, 100);
};
const selectOption = option => {
const {
split
} = props;
const {
value: mentionValue = ''
} = option;
const {
text,
selectionLocation
} = replaceWithMeasure(state.value, {
measureLocation: state.measureLocation,
targetText: mentionValue,
prefix: state.measurePrefix,
selectionStart: textarea.value.getSelectionStart(),
split
});
triggerChange(text);
stopMeasure(() => {
// We need restore the selection position
setInputSelection(textarea.value.input, selectionLocation);
});
emit('select', option, state.measurePrefix);
};
const setActiveIndex = activeIndex => {
state.activeIndex = activeIndex;
};
const getOptions = measureText => {
const targetMeasureText = measureText || state.measureText || '';
const {
filterOption
} = props;
const list = props.options.filter(option => {
/** Return all result if `filterOption` is false. */
if (!!filterOption === false) {
return true;
}
return filterOption(targetMeasureText, option);
});
return list;
};
const options = computed(() => {
return getOptions();
});
const focus = () => {
textarea.value.focus();
};
const blur = () => {
textarea.value.blur();
};
expose({
blur,
focus
});
provide(MentionsContextKey, {
activeIndex: toRef(state, 'activeIndex'),
setActiveIndex,
selectOption,
onFocus,
onBlur,
loading: toRef(props, 'loading')
});
onUpdated(() => {
nextTick(() => {
if (state.measuring) {
measure.value.scrollTop = textarea.value.getScrollTop();
}
});
});
return () => {
const {
measureLocation,
measurePrefix,
measuring
} = state;
const {
prefixCls,
placement,
transitionName,
getPopupContainer,
direction
} = props,
restProps = __rest(props, ["prefixCls", "placement", "transitionName", "getPopupContainer", "direction"]);
const {
class: className,
style
} = attrs,
otherAttrs = __rest(attrs, ["class", "style"]);
const inputProps = omit(restProps, ['value', 'prefix', 'split', 'validateSearch', 'filterOption', 'options', 'loading']);
const textareaProps = _extends(_extends(_extends({}, inputProps), otherAttrs), {
onChange: noop,
onSelect: noop,
value: state.value,
onInput: onChange,
onBlur: onInputBlur,
onKeydown: onKeyDown,
onKeyup: onKeyUp,
onFocus: onInputFocus,
onPressenter: onPressEnter
});
return _createVNode("div", {
"class": classNames(prefixCls, className),
"style": style
}, [_createVNode(BaseInput, _objectSpread(_objectSpread({}, textareaProps), {}, {
"ref": textarea,
"tag": "textarea"
}), null), measuring && _createVNode("div", {
"ref": measure,
"class": `${prefixCls}-measure`
}, [state.value.slice(0, measureLocation), _createVNode(KeywordTrigger, {
"prefixCls": prefixCls,
"transitionName": transitionName,
"dropdownClassName": props.dropdownClassName,
"placement": placement,
"options": measuring ? options.value : [],
"visible": true,
"direction": direction,
"getPopupContainer": getPopupContainer
}, {
default: () => [_createVNode("span", null, [measurePrefix])],
notFoundContent: slots.notFoundContent,
option: slots.option
}), state.value.slice(measureLocation + measurePrefix.length)])]);
};
}
});