ant-design-vue/components/vc-mentions/src/util.ts

125 lines
3.4 KiB
TypeScript

import type { MentionsProps } from './Mentions';
import type { OptionProps } from './Option';
/**
* Cut input selection into 2 part and return text before selection start
*/
export function getBeforeSelectionText(input: HTMLTextAreaElement) {
const { selectionStart } = input;
return input.value.slice(0, selectionStart);
}
interface MeasureIndex {
location: number;
prefix: string;
}
/**
* Find the last match prefix index
*/
export function getLastMeasureIndex(text: string, prefix: string | string[] = ''): MeasureIndex {
const prefixList: string[] = Array.isArray(prefix) ? prefix : [prefix];
return prefixList.reduce(
(lastMatch: MeasureIndex, prefixStr): MeasureIndex => {
const lastIndex = text.lastIndexOf(prefixStr);
if (lastIndex > lastMatch.location) {
return {
location: lastIndex,
prefix: prefixStr,
};
}
return lastMatch;
},
{ location: -1, prefix: '' },
);
}
interface MeasureConfig {
measureLocation: number;
prefix: string;
targetText: string;
selectionStart: number;
split: string;
}
function lower(char: string | undefined): string {
return (char || '').toLowerCase();
}
function reduceText(text: string, targetText: string, split: string) {
const firstChar = text[0];
if (!firstChar || firstChar === split) {
return text;
}
// Reuse rest text as it can
let restText = text;
const targetTextLen = targetText.length;
for (let i = 0; i < targetTextLen; i += 1) {
if (lower(restText[i]) !== lower(targetText[i])) {
restText = restText.slice(i);
break;
} else if (i === targetTextLen - 1) {
restText = restText.slice(targetTextLen);
}
}
return restText;
}
/**
* Paint targetText into current text:
* text: little@litest
* targetText: light
* => little @light test
*/
export function replaceWithMeasure(text: string, measureConfig: MeasureConfig) {
const { measureLocation, prefix, targetText, selectionStart, split } = measureConfig;
// Before text will append one space if have other text
let beforeMeasureText = text.slice(0, measureLocation);
if (beforeMeasureText[beforeMeasureText.length - split.length] === split) {
beforeMeasureText = beforeMeasureText.slice(0, beforeMeasureText.length - split.length);
}
if (beforeMeasureText) {
beforeMeasureText = `${beforeMeasureText}${split}`;
}
// Cut duplicate string with current targetText
let restText = reduceText(
text.slice(selectionStart),
targetText.slice(selectionStart - measureLocation - prefix.length),
split,
);
if (restText.slice(0, split.length) === split) {
restText = restText.slice(split.length);
}
const connectedStartText = `${beforeMeasureText}${prefix}${targetText}${split}`;
return {
text: `${connectedStartText}${restText}`,
selectionLocation: connectedStartText.length,
};
}
export function setInputSelection(input: HTMLTextAreaElement, location: number) {
input.setSelectionRange(location, location);
/**
* Reset caret into view.
* Since this function always called by user control, it's safe to focus element.
*/
input.blur();
input.focus();
}
export function validateSearch(text: string, props: MentionsProps) {
const { split } = props;
return !split || text.indexOf(split) === -1;
}
export function filterOption(input: string, { value = '' }: OptionProps): boolean {
const lowerCase = input.toLowerCase();
return value.toLowerCase().indexOf(lowerCase) !== -1;
}