textarea-pattern-handler
Version:
A TypeScript utility for handling textarea patterns and DOM manipulation
229 lines (228 loc) • 9.43 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = __importStar(require("react"));
const textareaWithPattern_1 = require("./textareaWithPattern");
require("textarea-pattern-handler/styles.css");
const PreTagTextarea = (0, react_1.forwardRef)(({ value, setValue, node, className = '', onRemoveTag, fixedText = '', defaultValue = '', id = 'preTagBox' }, ref) => {
const boxRef = (0, react_1.useRef)(null);
const [tagCounter, setTagCounter] = (0, react_1.useState)(0);
const [htmlValue, setHtmlValue] = (0, react_1.useState)('');
(0, react_1.useImperativeHandle)(ref, () => ({
addTag: (tagContent) => {
const box = boxRef.current;
if (!box)
return;
const selection = (0, textareaWithPattern_1.getCurrentSelection)();
if (!selection)
return;
const range = selection.getRangeAt(0);
const tagId = `tag-${tagCounter}`;
setTagCounter((prev) => prev + 1);
const tag = (0, textareaWithPattern_1.createTagElement)(tagContent, tagId);
if (box.contains(range.startContainer)) {
range.insertNode(tag);
const space = (0, textareaWithPattern_1.createSpaceNode)();
tag.after(space);
const newRange = (0, textareaWithPattern_1.createNewRange)(space);
(0, textareaWithPattern_1.setSelectionToRange)(newRange);
}
else {
box.appendChild(tag);
box.appendChild((0, textareaWithPattern_1.createSpaceNode)());
}
updateValues(box);
},
addText: (textContent) => {
const box = boxRef.current;
if (!box)
return;
const selection = (0, textareaWithPattern_1.getCurrentSelection)();
if (!selection)
return;
const range = selection.getRangeAt(0);
const textNode = (0, textareaWithPattern_1.createTextNode)(textContent);
if (box.contains(range.startContainer)) {
range.insertNode(textNode);
const space = (0, textareaWithPattern_1.createSpaceNode)();
textNode.after(space);
const newRange = (0, textareaWithPattern_1.createNewRange)(space);
(0, textareaWithPattern_1.setSelectionToRange)(newRange);
}
else {
box.appendChild(textNode);
box.appendChild((0, textareaWithPattern_1.createSpaceNode)());
}
updateValues(box);
},
}));
const handlePaste = (e) => {
e.preventDefault();
const text = e.clipboardData.getData('text/plain');
const fragments = text.split('\n');
const selection = (0, textareaWithPattern_1.getCurrentSelection)();
const range = selection === null || selection === void 0 ? void 0 : selection.getRangeAt(0);
const box = e.currentTarget;
if (!range || !box)
return;
range.deleteContents();
fragments.forEach((fragment, index) => {
const textNode = (0, textareaWithPattern_1.createTextNode)(fragment);
range.insertNode(textNode);
range.setStartAfter(textNode);
range.collapse(true);
if (index < fragments.length - 1) {
const br = (0, textareaWithPattern_1.createBRNode)();
range.insertNode(br);
range.setStartAfter(br);
range.collapse(true);
}
});
(0, textareaWithPattern_1.setSelectionToRange)(range);
updateValues(box);
};
const handleKeyDown = (e) => {
const box = e.currentTarget;
const selection = (0, textareaWithPattern_1.getCurrentSelection)();
if (!selection)
return;
switch (e.key) {
case 'Enter':
e.preventDefault();
handleEnterKey(box, selection);
break;
case 'Delete':
case 'Backspace':
e.preventDefault();
handleDeleteKey(box, selection);
break;
case 'a':
if (e.ctrlKey) {
e.preventDefault();
handleSelectAll(box);
}
break;
}
};
const handleEnterKey = (box, selection) => {
const range = selection.getRangeAt(0);
const br = (0, textareaWithPattern_1.createBRNode)();
const emptyText = (0, textareaWithPattern_1.createEmptyTextNode)();
range.insertNode(br);
br.after(emptyText);
const newRange = (0, textareaWithPattern_1.createNewRange)(emptyText);
(0, textareaWithPattern_1.setSelectionToRange)(newRange);
updateValues(box);
};
const handleDeleteKey = (box, selection) => {
const range = selection.getRangeAt(0);
const fullRange = document.createRange();
fullRange.selectNodeContents(box);
const isFullSelection = range.compareBoundaryPoints(Range.START_TO_START, fullRange) === 0 &&
range.compareBoundaryPoints(Range.END_TO_END, fullRange) === 0;
if (isFullSelection) {
clearContent(box);
return;
}
const patterns = box.querySelectorAll('span.tag');
let removedTags = false;
patterns.forEach((pattern) => {
if (range.intersectsNode(pattern)) {
pattern.remove();
removedTags = true;
}
});
if (removedTags) {
updateValues(box);
return;
}
document.execCommand('delete', false);
updateValues(box);
};
const handleSelectAll = (box) => {
const range = document.createRange();
range.selectNodeContents(box);
(0, textareaWithPattern_1.setSelectionToRange)(range);
};
const clearContent = (box) => {
box.innerHTML = '';
const emptyText = (0, textareaWithPattern_1.createTextNode)('');
box.appendChild(emptyText);
const range = (0, textareaWithPattern_1.createNewRange)(emptyText);
(0, textareaWithPattern_1.setSelectionToRange)(range);
updateValues(box);
};
const updateValues = (box) => {
const cleanText = (0, textareaWithPattern_1.cleanTextContent)(box.innerText);
const cleanHtml = (0, textareaWithPattern_1.cleanHtmlContent)(box.innerHTML);
(0, textareaWithPattern_1.removeTrailingBR)(box);
setValue(cleanText);
setHtmlValue(cleanHtml);
};
const handleRemoveTag = (e) => {
const target = e.target;
if (target.id.includes('tag')) {
const tagText = target.innerText;
const parentElement = target.parentElement;
target.remove();
onRemoveTag === null || onRemoveTag === void 0 ? void 0 : onRemoveTag(tagText);
updateValues(parentElement);
}
};
(0, react_1.useEffect)(() => {
if (boxRef.current) {
const initialContent = htmlValue || defaultValue;
if (initialContent) {
boxRef.current.innerHTML = initialContent.replace(fixedText, '').replace(/\n/g, '<br>');
}
}
}, [defaultValue, fixedText]);
const handleInput = (e) => {
const box = e.currentTarget;
updateValues(box);
};
const elementProps = {
ref: boxRef,
contentEditable: true,
onPaste: handlePaste,
onClick: handleRemoveTag,
onKeyDown: handleKeyDown,
onInput: handleInput,
id,
className: `${className} ${(value === null || value === void 0 ? void 0 : value.length) == 0 && 'placeholderOfTextareaWithPattern'} patternBox`,
};
return node ? react_1.default.cloneElement(node, elementProps) : react_1.default.createElement("div", Object.assign({}, elementProps));
});
exports.default = PreTagTextarea;