hongluan-ui
Version:
Hongluan Component Library for Vue 3
1 lines • 9.88 kB
Source Map (JSON)
{"version":3,"file":"mention.mjs","sources":["../../../../../../packages/components/mention/src/mention.vue"],"sourcesContent":["<template>\n <div ref=\"wrapperRef\" :class=\"namespace\">\n <hl-input\n v-bind=\"mergeProps(passInputProps, $attrs)\"\n ref=\"hlInputRef\"\n :model-value=\"modelValue\"\n @input=\"handleInputChange\"\n @keydown=\"handleInputKeyDown\"\n @focus=\"handleFocus\"\n @blur=\"handleBlur\"\n @mousedown=\"handleInputMouseDown\"\n >\n <template v-for=\"(_, name) in $slots\" #[name]=\"slotProps\">\n <slot :name=\"name\" v-bind=\"slotProps\"></slot>\n </template>\n </hl-input>\n <hl-tooltip\n ref=\"tooltipRef\"\n :visible=\"visible && (!!filteredOptions.length || loading)\"\n :popper-class=\"['mention-popper', popperClass]\"\n :popper-options=\"popperOptions\"\n :placement=\"computedPlacement\"\n :fallback-placements=\"computedFallbackPlacements\"\n effect=\"light\"\n pure\n :offset=\"offset\"\n :show-arrow=\"showArrow\"\n >\n <template #default>\n <div :style=\"cursorStyle\"></div>\n </template>\n <template #content>\n <hl-mention-dropdown\n ref=\"dropdownRef\"\n :options=\"filteredOptions\"\n :disabled=\"disabled\"\n :loading=\"loading\"\n @select=\"handleSelect\"\n @click.stop=\"hlInputRef?.focus\"\n >\n <template v-for=\"(_, name) in $slots\" #[name]=\"slotProps\">\n <slot :name=\"name\" v-bind=\"slotProps\"></slot>\n </template>\n </hl-mention-dropdown>\n </template>\n </hl-tooltip>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { computed, mergeProps, nextTick, ref } from 'vue'\nimport { pick } from 'lodash-unified'\nimport { useFocusController, useNamespace } from '@hongluan-ui/hooks'\nimport HlInput, { inputProps } from '@hongluan-ui/components/input'\nimport HlTooltip from '@hongluan-ui/components/tooltip'\nimport { UPDATE_MODEL_EVENT } from '@hongluan-ui/constants'\nimport { isFunction } from '@hongluan-ui/utils'\nimport { mentionEmits, mentionProps } from './mention'\nimport { getCursorPosition, getMentionCtx } from './helper'\nimport HlMentionDropdown from './mention-dropdown.vue'\n\nimport type { Placement } from '@popperjs/core'\nimport type { CSSProperties } from 'vue'\nimport type { InputInstance } from '@hongluan-ui/components/input'\nimport type { TooltipInstance } from '@hongluan-ui/components/tooltip'\nimport type { MentionCtx, MentionOption } from './types'\n\ndefineOptions({\n name: 'Mention',\n})\n\nconst props = defineProps(mentionProps)\nconst emit = defineEmits(mentionEmits)\n\nconst passInputProps = computed(() => pick(props, Object.keys(inputProps)))\n\nconst { namespace } = useNamespace('mention')\n\nconst hlInputRef = ref<InputInstance>()\nconst tooltipRef = ref<TooltipInstance>()\nconst dropdownRef = ref<InstanceType<typeof HlMentionDropdown>>()\n\nconst visible = ref(false)\nconst cursorStyle = ref<CSSProperties>()\nconst mentionCtx = ref<MentionCtx>()\n\nconst computedPlacement = computed<Placement>(() =>\n props.showArrow ? props.placement : `${props.placement}-start`,\n)\n\nconst computedFallbackPlacements = computed<Placement[]>(() =>\n props.showArrow ? ['bottom', 'top'] : ['bottom-start', 'top-start'],\n)\n\nconst filteredOptions = computed(() => {\n const { filterOption, options } = props\n if (!mentionCtx.value || !filterOption) return options\n return options.filter(option =>\n filterOption(mentionCtx.value!.pattern, option),\n )\n})\n\nconst handleInputChange = (value: string) => {\n emit('update:modelValue', value)\n syncAfterCursorMove()\n}\n\nconst handleInputKeyDown = (e: KeyboardEvent | Event) => {\n if (!('key' in e)) return\n if (hlInputRef.value?.isComposing) return\n if (['ArrowLeft', 'ArrowRight'].includes(e.key)) {\n syncAfterCursorMove()\n } else if (['ArrowUp', 'ArrowDown'].includes(e.key)) {\n if (!visible.value) return\n e.preventDefault()\n const direction = e.key === 'ArrowUp' ? 'prev' : 'next'\n dropdownRef.value?.navigateOptions(direction)\n } else if (['Enter'].includes(e.key)) {\n if (!visible.value) return\n e.preventDefault()\n if (dropdownRef.value?.hoverOption) {\n dropdownRef.value?.selectHoverOption()\n } else {\n visible.value = false\n }\n } else if (['Backspace'].includes(e.key)) {\n if (props.whole && mentionCtx.value) {\n const { splitIndex, selectionEnd, pattern, prefixIndex, prefix } =\n mentionCtx.value\n const inputEl = getInputEl()\n if (!inputEl) return\n const inputValue = inputEl.value\n const matchOption = props.options.find(item => item.value === pattern)\n const isWhole = isFunction(props.checkIsWhole)\n ? props.checkIsWhole(pattern, prefix)\n : matchOption\n if (isWhole && splitIndex !== -1 && splitIndex + 1 === selectionEnd) {\n e.preventDefault()\n const newValue =\n inputValue.slice(0, prefixIndex) + inputValue.slice(splitIndex + 1)\n emit(UPDATE_MODEL_EVENT, newValue)\n\n const newSelectionEnd = prefixIndex\n nextTick(() => {\n // input value is updated\n inputEl.selectionStart = newSelectionEnd\n inputEl.selectionEnd = newSelectionEnd\n syncDropdownVisible()\n })\n }\n }\n }\n}\n\nconst { wrapperRef, handleFocus, handleBlur } = useFocusController(hlInputRef, {\n afterFocus() {\n syncAfterCursorMove()\n },\n beforeBlur(event) {\n return tooltipRef.value?.isFocusInsideContent(event)\n },\n afterBlur() {\n visible.value = false\n },\n})\n\nconst handleInputMouseDown = () => {\n syncAfterCursorMove()\n}\n\nconst handleSelect = (item: MentionOption) => {\n if (!mentionCtx.value) return\n const inputEl = getInputEl()\n if (!inputEl) return\n const inputValue = inputEl.value\n const { split } = props\n\n const newEndPart = inputValue.slice(mentionCtx.value.end)\n const alreadySeparated = newEndPart.startsWith(split)\n const newMiddlePart = `${item.value}${alreadySeparated ? '' : split}`\n\n const newValue =\n inputValue.slice(0, mentionCtx.value.start) + newMiddlePart + newEndPart\n\n emit(UPDATE_MODEL_EVENT, newValue)\n emit('select', item, mentionCtx.value.prefix)\n\n const newSelectionEnd =\n mentionCtx.value.start + newMiddlePart.length + (alreadySeparated ? 1 : 0)\n\n nextTick(() => {\n // input value is updated\n inputEl.selectionStart = newSelectionEnd\n inputEl.selectionEnd = newSelectionEnd\n inputEl.focus()\n syncDropdownVisible()\n })\n}\n\nconst getInputEl = () =>\n props.nativeType === 'textarea'\n ? hlInputRef.value?.textarea\n : hlInputRef.value?.input\n\nconst syncAfterCursorMove = () => {\n // can't use nextTick(), get cursor position will be wrong\n setTimeout(() => {\n syncCursor()\n syncDropdownVisible()\n nextTick(() => tooltipRef.value?.updatePopper())\n }, 0)\n}\n\nconst syncCursor = () => {\n const inputEl = getInputEl()\n if (!inputEl) return\n\n const caretPosition = getCursorPosition(inputEl)\n const inputRect = inputEl.getBoundingClientRect()\n const elInputRect = hlInputRef.value!.$el.getBoundingClientRect()\n\n cursorStyle.value = {\n position: 'absolute',\n width: 0,\n height: `${caretPosition.height}px`,\n left: `${caretPosition.left + inputRect.left - elInputRect.left}px`,\n top: `${caretPosition.top + inputRect.top - elInputRect.top}px`,\n }\n}\n\nconst syncDropdownVisible = () => {\n const inputEl = getInputEl()\n if (document.activeElement !== inputEl) {\n visible.value = false\n return\n }\n const { prefix, split } = props\n mentionCtx.value = getMentionCtx(inputEl, prefix, split)\n if (mentionCtx.value && mentionCtx.value.splitIndex === -1) {\n visible.value = true\n emit('search', mentionCtx.value.pattern, mentionCtx.value.prefix)\n return\n }\n visible.value = false\n}\n\ndefineExpose({\n input: hlInputRef,\n tooltip: tooltipRef,\n})\n</script>\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;oCAmEc;AAAA,EACZ,MAAM;AACR;;;;;;;AAKA,UAAM,iBAAiB,SAAS,MAAM,KAAK,OAAO,OAAO,KAAK,UAAU,CAAC,CAAC;AAE1E,UAAM,EAAE,cAAc,aAAa,SAAS;AAE5C,UAAM,aAAa;AACnB,UAAM,aAAa;AACnB,UAAM,cAAc;AAEpB,UAAM,UAAU,IAAI,KAAK;AACzB,UAAM,cAAc;AACpB,UAAM,aAAa;AAEnB,UAAM,oBAAoB,SAAoB,MAC5C,MAAM,YAAY,MAAM,YAAY,GAAG,MAAM,iBAC/C;AAEA,UAAM,6BAA6B,SAAsB,MACvD,MAAM,YAAY,CAAC,UAAU,KAAK,IAAI,CAAC,gBAAgB,WAAW,CACpE;AAEA,UAAM,kBAAkB,SAAS,MAAM;AACrC,YAAM,EAAE,cAAc,YAAY;AAClC,UAAI,CAAC,WAAW,SAAS,CAAC;AAAc,eAAO;AAC/C,aAAO,QAAQ,OAAO,YACpB,aAAa,WAAW,MAAO,SAAS,MAAM,CAChD;AAAA,KACD;AAED,UAAM,oBAAoB,CAAC,UAAkB;AAC3C,WAAK,qBAAqB,KAAK;AAC/B;AAAoB;AAGtB,UAAM,qBAAqB,CAAC,MAA6B;AACvD,UAAI;AAAe;AACnB;AAAmC;AACnC;AACE;AAAoB,6BACC;AACrB,aAAK;AAAe;AACpB,UAAE;AACF,0BAAkB;AAClB,2BAAmB;AAAyB,yBAC3B,WAAW;AAC5B,aAAK;AAAe;AACpB,UAAE;AACF,wBAAgB;AACd,6BAAmB,kBAAkB;AAAA;AAErC;AAAgB;AAClB;AAEA,kBAAU,2BAA2B;AACnC,kBAAQ;AAER,gBAAM;AACN;AAAc;AACd;AACA,gBAAM,oBAAoB;AAC1B,gBAAM,2BAA2B,gBAC7B;AAEJ,2DAAiD,MAAM;AACrD,cAAE;AACF;AAEA,6CAAiC;AAEjC;AACA;AAEE;AACA,sBAAQ;AACR,kCAAoB;AAAA;AACrB;AACH;AACF;AACF;AAGF;AAA+E;AAE3E;AAAoB;AACtB;AAEE;AAAmD;AACrD,kBACY;AACV;AAAgB,MAClB;AAAA;AAGF;AACE;AAAoB,IACtB;AAEA,yBAAqB;AACnB;AAAuB;AACvB,sBAAgB;AAChB;AAAc;AACd;AACA;AAEA,YAAM,aAAa;AACnB,YAAM;AACN,YAAM,wBAAwB,2BAA2B;AAEzD,YAAM,gDACiC;AAEvC,+BAAyB;AACzB;AAEA,yCACa;AAEb,qBAAe;AAEb;AACA;AACA,gBAAQ;AACR,4BAAoB;AAAA;AACrB;AAGH;AAKA;AAEE,uBAAiB;AACf;AACA;AACA;AAA+C,UAC7C;AAAA;AAGN;AACE;AACA;AAAc;AAEd;AACA;AACA,YAAM;AAEN;AAAoB;AACR;AACH;AACkB,QACzB;AAA2D,8CACrB;AAAkB,MAC1D;AAAA;AAGF;AACE;AACA;AACE,gBAAQ;AACR;AAAA;AAEF;AACA;AACA,UAAI;AACF;AACA;AACA;AAAA;AAEF;AAAgB;AAGlB;AAAa,MACX;AAAO,eACE;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}