mention2.mjs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import { defineComponent, computed, ref, openBlock, createElementBlock, normalizeClass, unref, createVNode, mergeProps, createSlots, renderList, withCtx, renderSlot, normalizeProps, guardReactiveProps, createElementVNode, normalizeStyle, withModifiers, nextTick } from 'vue';
  2. import { pick } from 'lodash-unified';
  3. import { ElInput } from '../../input/index.mjs';
  4. import { ElTooltip } from '../../tooltip/index.mjs';
  5. import { mentionProps, mentionEmits } from './mention.mjs';
  6. import { getCursorPosition, getMentionCtx } from './helper.mjs';
  7. import ElMentionDropdown from './mention-dropdown2.mjs';
  8. import _export_sfc from '../../../_virtual/plugin-vue_export-helper.mjs';
  9. import { inputProps } from '../../input/src/input.mjs';
  10. import { useNamespace } from '../../../hooks/use-namespace/index.mjs';
  11. import { useFormDisabled } from '../../form/src/hooks/use-form-common-props.mjs';
  12. import { useId } from '../../../hooks/use-id/index.mjs';
  13. import { useFocusController } from '../../../hooks/use-focus-controller/index.mjs';
  14. import { UPDATE_MODEL_EVENT, INPUT_EVENT } from '../../../constants/event.mjs';
  15. import { EVENT_CODE } from '../../../constants/aria.mjs';
  16. import { isFunction } from '@vue/shared';
  17. const __default__ = defineComponent({
  18. name: "ElMention",
  19. inheritAttrs: false
  20. });
  21. const _sfc_main = /* @__PURE__ */ defineComponent({
  22. ...__default__,
  23. props: mentionProps,
  24. emits: mentionEmits,
  25. setup(__props, { expose, emit }) {
  26. const props = __props;
  27. const passInputProps = computed(() => pick(props, Object.keys(inputProps)));
  28. const ns = useNamespace("mention");
  29. const disabled = useFormDisabled();
  30. const contentId = useId();
  31. const elInputRef = ref();
  32. const tooltipRef = ref();
  33. const dropdownRef = ref();
  34. const visible = ref(false);
  35. const cursorStyle = ref();
  36. const mentionCtx = ref();
  37. const computedPlacement = computed(() => props.showArrow ? props.placement : `${props.placement}-start`);
  38. const computedFallbackPlacements = computed(() => props.showArrow ? ["bottom", "top"] : ["bottom-start", "top-start"]);
  39. const filteredOptions = computed(() => {
  40. const { filterOption, options } = props;
  41. if (!mentionCtx.value || !filterOption)
  42. return options;
  43. return options.filter((option) => filterOption(mentionCtx.value.pattern, option));
  44. });
  45. const dropdownVisible = computed(() => {
  46. return visible.value && (!!filteredOptions.value.length || props.loading);
  47. });
  48. const hoveringId = computed(() => {
  49. var _a;
  50. return `${contentId.value}-${(_a = dropdownRef.value) == null ? void 0 : _a.hoveringIndex}`;
  51. });
  52. const handleInputChange = (value) => {
  53. emit(UPDATE_MODEL_EVENT, value);
  54. emit(INPUT_EVENT, value);
  55. syncAfterCursorMove();
  56. };
  57. const handleInputKeyDown = (event) => {
  58. var _a, _b, _c, _d;
  59. if (!("code" in event) || ((_a = elInputRef.value) == null ? void 0 : _a.isComposing))
  60. return;
  61. switch (event.code) {
  62. case EVENT_CODE.left:
  63. case EVENT_CODE.right:
  64. syncAfterCursorMove();
  65. break;
  66. case EVENT_CODE.up:
  67. case EVENT_CODE.down:
  68. if (!visible.value)
  69. return;
  70. event.preventDefault();
  71. (_b = dropdownRef.value) == null ? void 0 : _b.navigateOptions(event.code === EVENT_CODE.up ? "prev" : "next");
  72. break;
  73. case EVENT_CODE.enter:
  74. case EVENT_CODE.numpadEnter:
  75. if (!visible.value)
  76. return;
  77. event.preventDefault();
  78. if ((_c = dropdownRef.value) == null ? void 0 : _c.hoverOption) {
  79. (_d = dropdownRef.value) == null ? void 0 : _d.selectHoverOption();
  80. } else {
  81. visible.value = false;
  82. }
  83. break;
  84. case EVENT_CODE.esc:
  85. if (!visible.value)
  86. return;
  87. event.preventDefault();
  88. visible.value = false;
  89. break;
  90. case EVENT_CODE.backspace:
  91. if (props.whole && mentionCtx.value) {
  92. const { splitIndex, selectionEnd, pattern, prefixIndex, prefix } = mentionCtx.value;
  93. const inputEl = getInputEl();
  94. if (!inputEl)
  95. return;
  96. const inputValue = inputEl.value;
  97. const matchOption = props.options.find((item) => item.value === pattern);
  98. const isWhole = isFunction(props.checkIsWhole) ? props.checkIsWhole(pattern, prefix) : matchOption;
  99. if (isWhole && splitIndex !== -1 && splitIndex + 1 === selectionEnd) {
  100. event.preventDefault();
  101. const newValue = inputValue.slice(0, prefixIndex) + inputValue.slice(splitIndex + 1);
  102. emit(UPDATE_MODEL_EVENT, newValue);
  103. emit(INPUT_EVENT, newValue);
  104. const newSelectionEnd = prefixIndex;
  105. nextTick(() => {
  106. inputEl.selectionStart = newSelectionEnd;
  107. inputEl.selectionEnd = newSelectionEnd;
  108. syncDropdownVisible();
  109. });
  110. }
  111. }
  112. }
  113. };
  114. const { wrapperRef } = useFocusController(elInputRef, {
  115. beforeFocus() {
  116. return disabled.value;
  117. },
  118. afterFocus() {
  119. syncAfterCursorMove();
  120. },
  121. beforeBlur(event) {
  122. var _a;
  123. return (_a = tooltipRef.value) == null ? void 0 : _a.isFocusInsideContent(event);
  124. },
  125. afterBlur() {
  126. visible.value = false;
  127. }
  128. });
  129. const handleInputMouseDown = () => {
  130. syncAfterCursorMove();
  131. };
  132. const handleSelect = (item) => {
  133. if (!mentionCtx.value)
  134. return;
  135. const inputEl = getInputEl();
  136. if (!inputEl)
  137. return;
  138. const inputValue = inputEl.value;
  139. const { split } = props;
  140. const newEndPart = inputValue.slice(mentionCtx.value.end);
  141. const alreadySeparated = newEndPart.startsWith(split);
  142. const newMiddlePart = `${item.value}${alreadySeparated ? "" : split}`;
  143. const newValue = inputValue.slice(0, mentionCtx.value.start) + newMiddlePart + newEndPart;
  144. emit(UPDATE_MODEL_EVENT, newValue);
  145. emit(INPUT_EVENT, newValue);
  146. emit("select", item, mentionCtx.value.prefix);
  147. const newSelectionEnd = mentionCtx.value.start + newMiddlePart.length + (alreadySeparated ? 1 : 0);
  148. nextTick(() => {
  149. inputEl.selectionStart = newSelectionEnd;
  150. inputEl.selectionEnd = newSelectionEnd;
  151. inputEl.focus();
  152. syncDropdownVisible();
  153. });
  154. };
  155. const getInputEl = () => {
  156. var _a, _b;
  157. return props.type === "textarea" ? (_a = elInputRef.value) == null ? void 0 : _a.textarea : (_b = elInputRef.value) == null ? void 0 : _b.input;
  158. };
  159. const syncAfterCursorMove = () => {
  160. setTimeout(() => {
  161. syncCursor();
  162. syncDropdownVisible();
  163. nextTick(() => {
  164. var _a;
  165. return (_a = tooltipRef.value) == null ? void 0 : _a.updatePopper();
  166. });
  167. }, 0);
  168. };
  169. const syncCursor = () => {
  170. const inputEl = getInputEl();
  171. if (!inputEl)
  172. return;
  173. const caretPosition = getCursorPosition(inputEl);
  174. const inputRect = inputEl.getBoundingClientRect();
  175. const elInputRect = elInputRef.value.$el.getBoundingClientRect();
  176. cursorStyle.value = {
  177. position: "absolute",
  178. width: 0,
  179. height: `${caretPosition.height}px`,
  180. left: `${caretPosition.left + inputRect.left - elInputRect.left}px`,
  181. top: `${caretPosition.top + inputRect.top - elInputRect.top}px`
  182. };
  183. };
  184. const syncDropdownVisible = () => {
  185. const inputEl = getInputEl();
  186. if (document.activeElement !== inputEl) {
  187. visible.value = false;
  188. return;
  189. }
  190. const { prefix, split } = props;
  191. mentionCtx.value = getMentionCtx(inputEl, prefix, split);
  192. if (mentionCtx.value && mentionCtx.value.splitIndex === -1) {
  193. visible.value = true;
  194. emit("search", mentionCtx.value.pattern, mentionCtx.value.prefix);
  195. return;
  196. }
  197. visible.value = false;
  198. };
  199. expose({
  200. input: elInputRef,
  201. tooltip: tooltipRef,
  202. dropdownVisible
  203. });
  204. return (_ctx, _cache) => {
  205. return openBlock(), createElementBlock("div", {
  206. ref_key: "wrapperRef",
  207. ref: wrapperRef,
  208. class: normalizeClass(unref(ns).b())
  209. }, [
  210. createVNode(unref(ElInput), mergeProps(mergeProps(unref(passInputProps), _ctx.$attrs), {
  211. ref_key: "elInputRef",
  212. ref: elInputRef,
  213. "model-value": _ctx.modelValue,
  214. disabled: unref(disabled),
  215. role: unref(dropdownVisible) ? "combobox" : void 0,
  216. "aria-activedescendant": unref(dropdownVisible) ? unref(hoveringId) || "" : void 0,
  217. "aria-controls": unref(dropdownVisible) ? unref(contentId) : void 0,
  218. "aria-expanded": unref(dropdownVisible) || void 0,
  219. "aria-label": _ctx.ariaLabel,
  220. "aria-autocomplete": unref(dropdownVisible) ? "none" : void 0,
  221. "aria-haspopup": unref(dropdownVisible) ? "listbox" : void 0,
  222. onInput: handleInputChange,
  223. onKeydown: handleInputKeyDown,
  224. onMousedown: handleInputMouseDown
  225. }), createSlots({
  226. _: 2
  227. }, [
  228. renderList(_ctx.$slots, (_, name) => {
  229. return {
  230. name,
  231. fn: withCtx((slotProps) => [
  232. renderSlot(_ctx.$slots, name, normalizeProps(guardReactiveProps(slotProps)))
  233. ])
  234. };
  235. })
  236. ]), 1040, ["model-value", "disabled", "role", "aria-activedescendant", "aria-controls", "aria-expanded", "aria-label", "aria-autocomplete", "aria-haspopup"]),
  237. createVNode(unref(ElTooltip), {
  238. ref_key: "tooltipRef",
  239. ref: tooltipRef,
  240. visible: unref(dropdownVisible),
  241. "popper-class": [unref(ns).e("popper"), _ctx.popperClass],
  242. "popper-options": _ctx.popperOptions,
  243. placement: unref(computedPlacement),
  244. "fallback-placements": unref(computedFallbackPlacements),
  245. effect: "light",
  246. pure: "",
  247. offset: _ctx.offset,
  248. "show-arrow": _ctx.showArrow
  249. }, {
  250. default: withCtx(() => [
  251. createElementVNode("div", {
  252. style: normalizeStyle(cursorStyle.value)
  253. }, null, 4)
  254. ]),
  255. content: withCtx(() => {
  256. var _a;
  257. return [
  258. createVNode(ElMentionDropdown, {
  259. ref_key: "dropdownRef",
  260. ref: dropdownRef,
  261. options: unref(filteredOptions),
  262. disabled: unref(disabled),
  263. loading: _ctx.loading,
  264. "content-id": unref(contentId),
  265. "aria-label": _ctx.ariaLabel,
  266. onSelect: handleSelect,
  267. onClick: withModifiers((_a = elInputRef.value) == null ? void 0 : _a.focus, ["stop"])
  268. }, createSlots({
  269. _: 2
  270. }, [
  271. renderList(_ctx.$slots, (_, name) => {
  272. return {
  273. name,
  274. fn: withCtx((slotProps) => [
  275. renderSlot(_ctx.$slots, name, normalizeProps(guardReactiveProps(slotProps)))
  276. ])
  277. };
  278. })
  279. ]), 1032, ["options", "disabled", "loading", "content-id", "aria-label", "onClick"])
  280. ];
  281. }),
  282. _: 3
  283. }, 8, ["visible", "popper-class", "popper-options", "placement", "fallback-placements", "offset", "show-arrow"])
  284. ], 2);
  285. };
  286. }
  287. });
  288. var Mention = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "mention.vue"]]);
  289. export { Mention as default };
  290. //# sourceMappingURL=mention2.mjs.map