310 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			310 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import { defineComponent, computed, ref, openBlock, createElementBlock, normalizeClass, unref, createVNode, mergeProps, createSlots, renderList, withCtx, renderSlot, normalizeProps, guardReactiveProps, createElementVNode, normalizeStyle, withModifiers, nextTick } from 'vue';
 | |
| import { pick } from 'lodash-unified';
 | |
| import { ElInput } from '../../input/index.mjs';
 | |
| import { ElTooltip } from '../../tooltip/index.mjs';
 | |
| import { mentionProps, mentionEmits, mentionDefaultProps } from './mention.mjs';
 | |
| import { getCursorPosition, getMentionCtx } from './helper.mjs';
 | |
| import ElMentionDropdown from './mention-dropdown2.mjs';
 | |
| import _export_sfc from '../../../_virtual/plugin-vue_export-helper.mjs';
 | |
| import { inputProps } from '../../input/src/input.mjs';
 | |
| import { useNamespace } from '../../../hooks/use-namespace/index.mjs';
 | |
| import { useFormDisabled } from '../../form/src/hooks/use-form-common-props.mjs';
 | |
| import { useId } from '../../../hooks/use-id/index.mjs';
 | |
| import { useFocusController } from '../../../hooks/use-focus-controller/index.mjs';
 | |
| import { UPDATE_MODEL_EVENT, INPUT_EVENT } from '../../../constants/event.mjs';
 | |
| import { EVENT_CODE } from '../../../constants/aria.mjs';
 | |
| import { isFunction } from '@vue/shared';
 | |
| 
 | |
| const __default__ = defineComponent({
 | |
|   name: "ElMention",
 | |
|   inheritAttrs: false
 | |
| });
 | |
| const _sfc_main = /* @__PURE__ */ defineComponent({
 | |
|   ...__default__,
 | |
|   props: mentionProps,
 | |
|   emits: mentionEmits,
 | |
|   setup(__props, { expose, emit }) {
 | |
|     const props = __props;
 | |
|     const passInputProps = computed(() => pick(props, Object.keys(inputProps)));
 | |
|     const ns = useNamespace("mention");
 | |
|     const disabled = useFormDisabled();
 | |
|     const contentId = useId();
 | |
|     const elInputRef = ref();
 | |
|     const tooltipRef = ref();
 | |
|     const dropdownRef = ref();
 | |
|     const visible = ref(false);
 | |
|     const cursorStyle = ref();
 | |
|     const mentionCtx = ref();
 | |
|     const computedPlacement = computed(() => props.showArrow ? props.placement : `${props.placement}-start`);
 | |
|     const computedFallbackPlacements = computed(() => props.showArrow ? ["bottom", "top"] : ["bottom-start", "top-start"]);
 | |
|     const aliasProps = computed(() => ({
 | |
|       ...mentionDefaultProps,
 | |
|       ...props.props
 | |
|     }));
 | |
|     const mapOption = (option) => {
 | |
|       const base = {
 | |
|         label: option[aliasProps.value.label],
 | |
|         value: option[aliasProps.value.value],
 | |
|         disabled: option[aliasProps.value.disabled]
 | |
|       };
 | |
|       return { ...option, ...base };
 | |
|     };
 | |
|     const options = computed(() => props.options.map(mapOption));
 | |
|     const filteredOptions = computed(() => {
 | |
|       const { filterOption } = props;
 | |
|       if (!mentionCtx.value || !filterOption)
 | |
|         return options.value;
 | |
|       return options.value.filter((option) => filterOption(mentionCtx.value.pattern, option));
 | |
|     });
 | |
|     const dropdownVisible = computed(() => {
 | |
|       return visible.value && (!!filteredOptions.value.length || props.loading);
 | |
|     });
 | |
|     const hoveringId = computed(() => {
 | |
|       var _a;
 | |
|       return `${contentId.value}-${(_a = dropdownRef.value) == null ? void 0 : _a.hoveringIndex}`;
 | |
|     });
 | |
|     const handleInputChange = (value) => {
 | |
|       emit(UPDATE_MODEL_EVENT, value);
 | |
|       emit(INPUT_EVENT, value);
 | |
|       syncAfterCursorMove();
 | |
|     };
 | |
|     const handleInputKeyDown = (event) => {
 | |
|       var _a, _b, _c, _d;
 | |
|       if (!("code" in event) || ((_a = elInputRef.value) == null ? void 0 : _a.isComposing))
 | |
|         return;
 | |
|       switch (event.code) {
 | |
|         case EVENT_CODE.left:
 | |
|         case EVENT_CODE.right:
 | |
|           syncAfterCursorMove();
 | |
|           break;
 | |
|         case EVENT_CODE.up:
 | |
|         case EVENT_CODE.down:
 | |
|           if (!visible.value)
 | |
|             return;
 | |
|           event.preventDefault();
 | |
|           (_b = dropdownRef.value) == null ? void 0 : _b.navigateOptions(event.code === EVENT_CODE.up ? "prev" : "next");
 | |
|           break;
 | |
|         case EVENT_CODE.enter:
 | |
|         case EVENT_CODE.numpadEnter:
 | |
|           if (!visible.value)
 | |
|             return;
 | |
|           event.preventDefault();
 | |
|           if ((_c = dropdownRef.value) == null ? void 0 : _c.hoverOption) {
 | |
|             (_d = dropdownRef.value) == null ? void 0 : _d.selectHoverOption();
 | |
|           } else {
 | |
|             visible.value = false;
 | |
|           }
 | |
|           break;
 | |
|         case EVENT_CODE.esc:
 | |
|           if (!visible.value)
 | |
|             return;
 | |
|           event.preventDefault();
 | |
|           visible.value = false;
 | |
|           break;
 | |
|         case EVENT_CODE.backspace:
 | |
|           if (props.whole && mentionCtx.value) {
 | |
|             const { splitIndex, selectionEnd, pattern, prefixIndex, prefix } = mentionCtx.value;
 | |
|             const inputEl = getInputEl();
 | |
|             if (!inputEl)
 | |
|               return;
 | |
|             const inputValue = inputEl.value;
 | |
|             const matchOption = options.value.find((item) => item.value === pattern);
 | |
|             const isWhole = isFunction(props.checkIsWhole) ? props.checkIsWhole(pattern, prefix) : matchOption;
 | |
|             if (isWhole && splitIndex !== -1 && splitIndex + 1 === selectionEnd) {
 | |
|               event.preventDefault();
 | |
|               const newValue = inputValue.slice(0, prefixIndex) + inputValue.slice(splitIndex + 1);
 | |
|               emit(UPDATE_MODEL_EVENT, newValue);
 | |
|               emit(INPUT_EVENT, newValue);
 | |
|               emit("whole-remove", pattern, prefix);
 | |
|               const newSelectionEnd = prefixIndex;
 | |
|               nextTick(() => {
 | |
|                 inputEl.selectionStart = newSelectionEnd;
 | |
|                 inputEl.selectionEnd = newSelectionEnd;
 | |
|                 syncDropdownVisible();
 | |
|               });
 | |
|             }
 | |
|           }
 | |
|       }
 | |
|     };
 | |
|     const { wrapperRef } = useFocusController(elInputRef, {
 | |
|       disabled,
 | |
|       afterFocus() {
 | |
|         syncAfterCursorMove();
 | |
|       },
 | |
|       beforeBlur(event) {
 | |
|         var _a;
 | |
|         return (_a = tooltipRef.value) == null ? void 0 : _a.isFocusInsideContent(event);
 | |
|       },
 | |
|       afterBlur() {
 | |
|         visible.value = false;
 | |
|       }
 | |
|     });
 | |
|     const handleInputMouseDown = () => {
 | |
|       syncAfterCursorMove();
 | |
|     };
 | |
|     const getOriginalOption = (mentionOption) => {
 | |
|       return props.options.find((option) => {
 | |
|         return mentionOption.value === option[aliasProps.value.value];
 | |
|       });
 | |
|     };
 | |
|     const handleSelect = (item) => {
 | |
|       if (!mentionCtx.value)
 | |
|         return;
 | |
|       const inputEl = getInputEl();
 | |
|       if (!inputEl)
 | |
|         return;
 | |
|       const inputValue = inputEl.value;
 | |
|       const { split } = props;
 | |
|       const newEndPart = inputValue.slice(mentionCtx.value.end);
 | |
|       const alreadySeparated = newEndPart.startsWith(split);
 | |
|       const newMiddlePart = `${item.value}${alreadySeparated ? "" : split}`;
 | |
|       const newValue = inputValue.slice(0, mentionCtx.value.start) + newMiddlePart + newEndPart;
 | |
|       emit(UPDATE_MODEL_EVENT, newValue);
 | |
|       emit(INPUT_EVENT, newValue);
 | |
|       emit("select", getOriginalOption(item), mentionCtx.value.prefix);
 | |
|       const newSelectionEnd = mentionCtx.value.start + newMiddlePart.length + (alreadySeparated ? 1 : 0);
 | |
|       nextTick(() => {
 | |
|         inputEl.selectionStart = newSelectionEnd;
 | |
|         inputEl.selectionEnd = newSelectionEnd;
 | |
|         inputEl.focus();
 | |
|         syncDropdownVisible();
 | |
|       });
 | |
|     };
 | |
|     const getInputEl = () => {
 | |
|       var _a, _b;
 | |
|       return props.type === "textarea" ? (_a = elInputRef.value) == null ? void 0 : _a.textarea : (_b = elInputRef.value) == null ? void 0 : _b.input;
 | |
|     };
 | |
|     const syncAfterCursorMove = () => {
 | |
|       setTimeout(() => {
 | |
|         syncCursor();
 | |
|         syncDropdownVisible();
 | |
|         nextTick(() => {
 | |
|           var _a;
 | |
|           return (_a = tooltipRef.value) == null ? void 0 : _a.updatePopper();
 | |
|         });
 | |
|       }, 0);
 | |
|     };
 | |
|     const syncCursor = () => {
 | |
|       const inputEl = getInputEl();
 | |
|       if (!inputEl)
 | |
|         return;
 | |
|       const caretPosition = getCursorPosition(inputEl);
 | |
|       const inputRect = inputEl.getBoundingClientRect();
 | |
|       const wrapperRect = wrapperRef.value.getBoundingClientRect();
 | |
|       cursorStyle.value = {
 | |
|         position: "absolute",
 | |
|         width: 0,
 | |
|         height: `${caretPosition.height}px`,
 | |
|         left: `${caretPosition.left + inputRect.left - wrapperRect.left}px`,
 | |
|         top: `${caretPosition.top + inputRect.top - wrapperRect.top}px`
 | |
|       };
 | |
|     };
 | |
|     const syncDropdownVisible = () => {
 | |
|       const inputEl = getInputEl();
 | |
|       if (document.activeElement !== inputEl) {
 | |
|         visible.value = false;
 | |
|         return;
 | |
|       }
 | |
|       const { prefix, split } = props;
 | |
|       mentionCtx.value = getMentionCtx(inputEl, prefix, split);
 | |
|       if (mentionCtx.value && mentionCtx.value.splitIndex === -1) {
 | |
|         visible.value = true;
 | |
|         emit("search", mentionCtx.value.pattern, mentionCtx.value.prefix);
 | |
|         return;
 | |
|       }
 | |
|       visible.value = false;
 | |
|     };
 | |
|     expose({
 | |
|       input: elInputRef,
 | |
|       tooltip: tooltipRef,
 | |
|       dropdownVisible
 | |
|     });
 | |
|     return (_ctx, _cache) => {
 | |
|       return openBlock(), createElementBlock("div", {
 | |
|         ref_key: "wrapperRef",
 | |
|         ref: wrapperRef,
 | |
|         class: normalizeClass(unref(ns).b())
 | |
|       }, [
 | |
|         createVNode(unref(ElInput), mergeProps(mergeProps(unref(passInputProps), _ctx.$attrs), {
 | |
|           ref_key: "elInputRef",
 | |
|           ref: elInputRef,
 | |
|           "model-value": _ctx.modelValue,
 | |
|           disabled: unref(disabled),
 | |
|           role: unref(dropdownVisible) ? "combobox" : void 0,
 | |
|           "aria-activedescendant": unref(dropdownVisible) ? unref(hoveringId) || "" : void 0,
 | |
|           "aria-controls": unref(dropdownVisible) ? unref(contentId) : void 0,
 | |
|           "aria-expanded": unref(dropdownVisible) || void 0,
 | |
|           "aria-label": _ctx.ariaLabel,
 | |
|           "aria-autocomplete": unref(dropdownVisible) ? "none" : void 0,
 | |
|           "aria-haspopup": unref(dropdownVisible) ? "listbox" : void 0,
 | |
|           onInput: handleInputChange,
 | |
|           onKeydown: handleInputKeyDown,
 | |
|           onMousedown: handleInputMouseDown
 | |
|         }), createSlots({
 | |
|           _: 2
 | |
|         }, [
 | |
|           renderList(_ctx.$slots, (_, name) => {
 | |
|             return {
 | |
|               name,
 | |
|               fn: withCtx((slotProps) => [
 | |
|                 renderSlot(_ctx.$slots, name, normalizeProps(guardReactiveProps(slotProps)))
 | |
|               ])
 | |
|             };
 | |
|           })
 | |
|         ]), 1040, ["model-value", "disabled", "role", "aria-activedescendant", "aria-controls", "aria-expanded", "aria-label", "aria-autocomplete", "aria-haspopup"]),
 | |
|         createVNode(unref(ElTooltip), {
 | |
|           ref_key: "tooltipRef",
 | |
|           ref: tooltipRef,
 | |
|           visible: unref(dropdownVisible),
 | |
|           "popper-class": [unref(ns).e("popper"), _ctx.popperClass],
 | |
|           "popper-options": _ctx.popperOptions,
 | |
|           placement: unref(computedPlacement),
 | |
|           "fallback-placements": unref(computedFallbackPlacements),
 | |
|           effect: "light",
 | |
|           pure: "",
 | |
|           offset: _ctx.offset,
 | |
|           "show-arrow": _ctx.showArrow
 | |
|         }, {
 | |
|           default: withCtx(() => [
 | |
|             createElementVNode("div", {
 | |
|               style: normalizeStyle(cursorStyle.value)
 | |
|             }, null, 4)
 | |
|           ]),
 | |
|           content: withCtx(() => {
 | |
|             var _a;
 | |
|             return [
 | |
|               createVNode(ElMentionDropdown, {
 | |
|                 ref_key: "dropdownRef",
 | |
|                 ref: dropdownRef,
 | |
|                 options: unref(filteredOptions),
 | |
|                 disabled: unref(disabled),
 | |
|                 loading: _ctx.loading,
 | |
|                 "content-id": unref(contentId),
 | |
|                 "aria-label": _ctx.ariaLabel,
 | |
|                 onSelect: handleSelect,
 | |
|                 onClick: withModifiers((_a = elInputRef.value) == null ? void 0 : _a.focus, ["stop"])
 | |
|               }, createSlots({
 | |
|                 _: 2
 | |
|               }, [
 | |
|                 renderList(_ctx.$slots, (_, name) => {
 | |
|                   return {
 | |
|                     name,
 | |
|                     fn: withCtx((slotProps) => [
 | |
|                       renderSlot(_ctx.$slots, name, normalizeProps(guardReactiveProps(slotProps)))
 | |
|                     ])
 | |
|                   };
 | |
|                 })
 | |
|               ]), 1032, ["options", "disabled", "loading", "content-id", "aria-label", "onClick"])
 | |
|             ];
 | |
|           }),
 | |
|           _: 3
 | |
|         }, 8, ["visible", "popper-class", "popper-options", "placement", "fallback-placements", "offset", "show-arrow"])
 | |
|       ], 2);
 | |
|     };
 | |
|   }
 | |
| });
 | |
| var Mention = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "mention.vue"]]);
 | |
| 
 | |
| export { Mention as default };
 | |
| //# sourceMappingURL=mention2.mjs.map
 |