naive-ui
Version:
A Vue 3 Component Library. Fairly Complete, Theme Customizable, Uses TypeScript, Fast
372 lines (371 loc) • 17 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.mentionProps = void 0;
const vue_1 = require("vue");
const treemate_1 = require("treemate");
const vueuc_1 = require("vueuc");
const vooks_1 = require("vooks");
const input_1 = require("../../input");
const _internal_1 = require("../../_internal");
const _utils_1 = require("../../_utils");
const _mixins_1 = require("../../_mixins");
const styles_1 = require("../styles");
const utils_1 = require("./utils");
const index_cssr_1 = __importDefault(require("./styles/index.cssr"));
exports.mentionProps = Object.assign(Object.assign({}, _mixins_1.useTheme.props), { to: _utils_1.useAdjustedTo.propTo, autosize: [Boolean, Object], options: {
type: Array,
default: []
}, filter: {
type: Function,
default: (pattern, option) => {
if (!pattern)
return true;
if (typeof option.label === 'string') {
return option.label.startsWith(pattern);
}
if (typeof option.value === 'string') {
return option.value.startsWith(pattern);
}
return false;
}
}, type: {
type: String,
default: 'text'
}, separator: {
type: String,
validator: (separator) => {
if (separator.length !== 1) {
(0, _utils_1.warn)('mention', '`separator`\'s length must be 1.');
return false;
}
return true;
},
default: ' '
}, bordered: {
type: Boolean,
default: undefined
}, disabled: Boolean, value: String, defaultValue: {
type: String,
default: ''
}, loading: Boolean, prefix: {
type: [String, Array],
default: '@'
}, placeholder: {
type: String,
default: ''
}, placement: {
type: String,
default: 'bottom-start'
}, size: String, renderLabel: Function, status: String, 'onUpdate:show': [Array, Function], onUpdateShow: [Array, Function], 'onUpdate:value': [Array, Function], onUpdateValue: [Array, Function], onSearch: Function, onSelect: Function, onFocus: Function, onBlur: Function,
// private
internalDebug: Boolean });
exports.default = (0, vue_1.defineComponent)({
name: 'Mention',
props: exports.mentionProps,
setup(props) {
const { namespaceRef, mergedClsPrefixRef, mergedBorderedRef, inlineThemeDisabled } = (0, _mixins_1.useConfig)(props);
const themeRef = (0, _mixins_1.useTheme)('Mention', '-mention', index_cssr_1.default, styles_1.mentionLight, props, mergedClsPrefixRef);
const formItem = (0, _mixins_1.useFormItem)(props);
const inputInstRef = (0, vue_1.ref)(null);
const cursorRef = (0, vue_1.ref)(null);
const followerRef = (0, vue_1.ref)(null);
const wrapperElRef = (0, vue_1.ref)(null);
const partialPatternRef = (0, vue_1.ref)('');
let cachedPrefix = null;
// cached pattern end is for partial pattern
// for example @abc|def
// end is after `c`
let cachedPartialPatternStart = null;
let cachedPartialPatternEnd = null;
const filteredOptionsRef = (0, vue_1.computed)(() => {
const { value: pattern } = partialPatternRef;
return props.options.filter(option => props.filter(pattern, option));
});
const treeMateRef = (0, vue_1.computed)(() => {
return (0, treemate_1.createTreeMate)(filteredOptionsRef.value, {
getKey: (v) => {
return v.value;
}
});
});
const selectMenuInstRef = (0, vue_1.ref)(null);
const showMenuRef = (0, vue_1.ref)(false);
const uncontrolledValueRef = (0, vue_1.ref)(props.defaultValue);
const controlledValueRef = (0, vue_1.toRef)(props, 'value');
const mergedValueRef = (0, vooks_1.useMergedState)(controlledValueRef, uncontrolledValueRef);
const cssVarsRef = (0, vue_1.computed)(() => {
const { self: { menuBoxShadow } } = themeRef.value;
return {
'--n-menu-box-shadow': menuBoxShadow
};
});
const themeClassHandle = inlineThemeDisabled
? (0, _mixins_1.useThemeClass)('mention', undefined, cssVarsRef, props)
: undefined;
function doUpdateShowMenu(show) {
if (props.disabled)
return;
const { onUpdateShow, 'onUpdate:show': _onUpdateShow } = props;
if (onUpdateShow)
(0, _utils_1.call)(onUpdateShow, show);
if (_onUpdateShow)
(0, _utils_1.call)(_onUpdateShow, show);
if (!show) {
cachedPrefix = null;
cachedPartialPatternStart = null;
cachedPartialPatternEnd = null;
}
showMenuRef.value = show;
}
function doUpdateValue(value) {
const { onUpdateValue, 'onUpdate:value': _onUpdateValue } = props;
const { nTriggerFormChange, nTriggerFormInput } = formItem;
if (_onUpdateValue) {
(0, _utils_1.call)(_onUpdateValue, value);
}
if (onUpdateValue) {
(0, _utils_1.call)(onUpdateValue, value);
}
nTriggerFormInput();
nTriggerFormChange();
uncontrolledValueRef.value = value;
}
function getInputEl() {
return props.type === 'text'
? inputInstRef.value.inputElRef
: inputInstRef.value.textareaElRef;
}
function deriveShowMenu() {
var _a;
const inputEl = getInputEl();
if (document.activeElement !== inputEl) {
doUpdateShowMenu(false);
return;
}
const { selectionEnd } = inputEl;
if (selectionEnd === null) {
doUpdateShowMenu(false);
return;
}
const inputValue = inputEl.value;
const { separator } = props;
const { prefix } = props;
const prefixArray = typeof prefix === 'string' ? [prefix] : prefix;
for (let i = selectionEnd - 1; i >= 0; --i) {
const char = inputValue[i];
if (char === separator || char === '\n' || char === '\r') {
doUpdateShowMenu(false);
return;
}
if (prefixArray.includes(char)) {
const partialPattern = inputValue.slice(i + 1, selectionEnd);
doUpdateShowMenu(true);
(_a = props.onSearch) === null || _a === void 0 ? void 0 : _a.call(props, partialPattern, char);
partialPatternRef.value = partialPattern;
cachedPrefix = char;
cachedPartialPatternStart = i + 1;
cachedPartialPatternEnd = selectionEnd;
return;
}
}
doUpdateShowMenu(false);
}
function syncCursor() {
const { value: cursorAnchor } = cursorRef;
if (!cursorAnchor)
return;
const inputEl = getInputEl();
const cursorPos = (0, utils_1.getRelativePosition)(inputEl);
const inputRect = inputEl.getBoundingClientRect();
const wrapperRect = wrapperElRef.value.getBoundingClientRect();
cursorAnchor.style.left = `${cursorPos.left + inputRect.left - wrapperRect.left}px`;
cursorAnchor.style.top = `${cursorPos.top + inputRect.top - wrapperRect.top}px`;
cursorAnchor.style.height = `${cursorPos.height}px`;
}
function syncPosition() {
var _a;
if (!showMenuRef.value)
return;
(_a = followerRef.value) === null || _a === void 0 ? void 0 : _a.syncPosition();
}
function handleInputUpdateValue(value) {
doUpdateValue(value);
// Vue update is mirco task.
// So DOM must have been done when sync start in marco task.
// I can't use nextTick(), Chrome doesn't update scrollLeft of INPUT
// element is immediatelly updated. The behavior is wired but that's what
// happens.
syncAfterCursorMove();
}
function syncAfterCursorMove() {
setTimeout(() => {
syncCursor();
deriveShowMenu();
void (0, vue_1.nextTick)().then(syncPosition);
}, 0);
}
function handleInputKeyDown(e) {
var _a, _b;
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
if ((_a = inputInstRef.value) === null || _a === void 0 ? void 0 : _a.isCompositing)
return;
syncAfterCursorMove();
}
else if (e.key === 'ArrowUp'
|| e.key === 'ArrowDown'
|| e.key === 'Enter') {
if ((_b = inputInstRef.value) === null || _b === void 0 ? void 0 : _b.isCompositing)
return;
const { value: selectMenuInst } = selectMenuInstRef;
if (showMenuRef.value) {
if (selectMenuInst) {
e.preventDefault();
if (e.key === 'ArrowUp') {
selectMenuInst.prev();
}
else if (e.key === 'ArrowDown') {
selectMenuInst.next();
}
else {
// Enter
const pendingOptionTmNode = selectMenuInst.getPendingTmNode();
if (pendingOptionTmNode) {
handleSelect(pendingOptionTmNode);
}
else {
doUpdateShowMenu(false);
}
}
}
}
else {
syncAfterCursorMove();
}
}
}
function handleInputFocus(e) {
const { onFocus } = props;
onFocus === null || onFocus === void 0 ? void 0 : onFocus(e);
const { nTriggerFormFocus } = formItem;
nTriggerFormFocus();
syncAfterCursorMove();
}
function focus() {
var _a;
(_a = inputInstRef.value) === null || _a === void 0 ? void 0 : _a.focus();
}
function blur() {
var _a;
(_a = inputInstRef.value) === null || _a === void 0 ? void 0 : _a.blur();
}
function handleInputBlur(e) {
const { onBlur } = props;
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
const { nTriggerFormBlur } = formItem;
nTriggerFormBlur();
doUpdateShowMenu(false);
}
function handleSelect(tmNode) {
var _a;
if (cachedPrefix === null
|| cachedPartialPatternStart === null
|| cachedPartialPatternEnd === null) {
if (process.env.NODE_ENV !== 'production') {
(0, _utils_1.warn)('mention', 'Cache works unexpectly, this is probably a bug. Please create an issue.');
}
return;
}
const { rawNode: { value = '' } } = tmNode;
const inputEl = getInputEl();
const inputValue = inputEl.value;
const { separator } = props;
const nextEndPart = inputValue.slice(cachedPartialPatternEnd);
const alreadySeparated = nextEndPart.startsWith(separator);
const nextMiddlePart = `${value}${alreadySeparated ? '' : separator}`;
doUpdateValue(inputValue.slice(0, cachedPartialPatternStart)
+ nextMiddlePart
+ nextEndPart);
(_a = props.onSelect) === null || _a === void 0 ? void 0 : _a.call(props, tmNode.rawNode, cachedPrefix);
const nextSelectionEnd = cachedPartialPatternStart
+ nextMiddlePart.length
+ (alreadySeparated ? 1 : 0);
void (0, vue_1.nextTick)().then(() => {
// input value is updated
inputEl.selectionStart = nextSelectionEnd;
inputEl.selectionEnd = nextSelectionEnd;
deriveShowMenu();
});
}
function handleInputMouseDown() {
if (!props.disabled) {
syncAfterCursorMove();
}
}
return {
namespace: namespaceRef,
mergedClsPrefix: mergedClsPrefixRef,
mergedBordered: mergedBorderedRef,
mergedSize: formItem.mergedSizeRef,
mergedStatus: formItem.mergedStatusRef,
mergedTheme: themeRef,
treeMate: treeMateRef,
selectMenuInstRef,
inputInstRef,
cursorRef,
followerRef,
wrapperElRef,
showMenu: showMenuRef,
adjustedTo: (0, _utils_1.useAdjustedTo)(props),
isMounted: (0, vooks_1.useIsMounted)(),
mergedValue: mergedValueRef,
handleInputFocus,
handleInputBlur,
handleInputUpdateValue,
handleInputKeyDown,
handleSelect,
handleInputMouseDown,
focus,
blur,
cssVars: inlineThemeDisabled ? undefined : cssVarsRef,
themeClass: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.themeClass,
onRender: themeClassHandle === null || themeClassHandle === void 0 ? void 0 : themeClassHandle.onRender
};
},
render() {
const { mergedTheme, mergedClsPrefix, $slots } = this;
return ((0, vue_1.h)("div", { class: `${mergedClsPrefix}-mention`, ref: "wrapperElRef" },
(0, vue_1.h)(input_1.NInput, { status: this.mergedStatus, themeOverrides: mergedTheme.peerOverrides.Input, theme: mergedTheme.peers.Input, size: this.mergedSize, autosize: this.autosize, type: this.type, ref: "inputInstRef", placeholder: this.placeholder, onMousedown: this.handleInputMouseDown, onUpdateValue: this.handleInputUpdateValue, onKeydown: this.handleInputKeyDown, onFocus: this.handleInputFocus, onBlur: this.handleInputBlur, bordered: this.mergedBordered, disabled: this.disabled, value: this.mergedValue }),
(0, vue_1.h)(vueuc_1.VBinder, null, {
default: () => [
(0, vue_1.h)(vueuc_1.VTarget, null, {
default: () => {
const style = {
position: 'absolute',
width: 0
};
if (process.env.NODE_ENV !== 'production' && this.internalDebug) {
style.width = '1px';
style.background = 'red';
}
return (0, vue_1.h)("div", { style: style, ref: "cursorRef" });
}
}),
(0, vue_1.h)(vueuc_1.VFollower, { ref: "followerRef", placement: this.placement, show: this.showMenu, containerClass: this.namespace, to: this.adjustedTo, teleportDisabled: this.adjustedTo === _utils_1.useAdjustedTo.tdkey }, {
default: () => ((0, vue_1.h)(vue_1.Transition, { name: "fade-in-scale-up-transition", appear: this.isMounted }, {
default: () => {
const { mergedTheme, onRender } = this;
onRender === null || onRender === void 0 ? void 0 : onRender();
return this.showMenu ? ((0, vue_1.h)(_internal_1.NInternalSelectMenu, { clsPrefix: mergedClsPrefix, theme: mergedTheme.peers.InternalSelectMenu, themeOverrides: mergedTheme.peerOverrides.InternalSelectMenu, autoPending: true, ref: "selectMenuInstRef", class: [
`${mergedClsPrefix}-mention-menu`,
this.themeClass
], loading: this.loading, treeMate: this.treeMate, virtualScroll: false, style: this.cssVars, onToggle: this.handleSelect, renderLabel: this.renderLabel }, $slots)) : null;
}
}))
})
]
})));
}
});
;