apphouse
Version:
Component library for React that uses observable state management and theme-able components.
131 lines (122 loc) • 3.66 kB
text/typescript
/**
* A class to help with text selection and manipulation of textareas and inputs
*/
export class TextSelection {
elm: HTMLTextAreaElement;
start: number;
end: number;
value: string;
constructor(elm: HTMLTextAreaElement) {
const { selectionStart, selectionEnd } = elm;
this.elm = elm;
this.start = selectionStart;
this.end = selectionEnd;
this.value = this.elm.value;
}
/**
* Sets the position of the text selection within an element.
*
* @param {number} [start] - The start position of the selection.
* @param {number} [end] - The end position of the selection.
* @returns {Object} - Returns the instance of the object.
*/
position(start?: number, end?: number) {
const { selectionStart, selectionEnd } = this.elm;
this.start =
typeof start === 'number' && !isNaN(start) ? start : selectionStart;
this.end = typeof end === 'number' && !isNaN(end) ? end : selectionEnd;
this.elm.selectionStart = this.start;
this.elm.selectionEnd = this.end;
return this;
}
/**
* Inserts text at the current cursor position
* @param text the text to insert
* @returns the TextSelection instance
*/
insertText(text: string) {
// Most of the used APIs only work with the field selected
this.elm.focus();
this.elm.setRangeText(text);
this.value = this.elm.value;
this.position();
return this;
}
/**
* Gets the selected text value from a textarea / input
* @param start number start index
* @param end number end index
* @returns the selected text
*/
getSelectedValue(start?: number, end?: number) {
const { selectionStart, selectionEnd } = this.elm;
return this.value.slice(
typeof start === 'number' && !isNaN(start) ? start : selectionStart,
typeof end === 'number' && !isNaN(end) ? end : selectionEnd
);
}
/**
* Gets the start index of the current line where the text is selected
* @returns the start index of the current line
*/
getLineStartNumber() {
let start = this.start;
while (start > 0) {
start--;
if (this.value.charAt(start) === '\n') {
start++;
break;
}
}
return start;
}
/** Indent on new lines */
getIndentText() {
const start = this.getLineStartNumber();
const str = this.getSelectedValue(start);
let indent = '';
str.replace(/(^(\s)+)/, (_str, old) => (indent = old));
return indent;
}
lineStarInsert(text: string) {
if (text) {
const oldStart = this.start;
const start = this.getLineStartNumber();
const str = this.getSelectedValue(start);
this.position(start, this.end)
.insertText(
str
.split('\n')
.map((txt) => text + txt)
.join('\n')
)
.position(oldStart + text.length, this.end);
}
return this;
}
lineStarRemove(text: string) {
if (text) {
const oldStart = this.start;
const start = this.getLineStartNumber();
const str = this.getSelectedValue(start);
const reg = new RegExp(`^${text}`, 'g');
let newStart = oldStart - text.length;
if (!reg.test(str)) {
newStart = oldStart;
}
this.position(start, this.end)
.insertText(
str
.split('\n')
.map((txt) => txt.replace(reg, ''))
.join('\n')
)
.position(newStart, this.end + newStart - oldStart);
}
}
/** Notify any possible listeners of the change */
notifyChange() {
const event = new Event('input', { bubbles: true, cancelable: false });
this.elm.dispatchEvent(event);
}
}