ContentEditable - team-yaza/mozi-client GitHub Wiki
MOZI์์ Todo์ Title์ด๋ Description๊ฐ์ ์์๋ฅผ ์์ ํ ๋ input์ผ๋ก ํ ๊ฒ์ธ๊ฐ div ํ๊ทธ์ contentEditable์ด๋ผ๋ ์์ฑ์ ์ฌ์ฉํ ๊ฒ์ธ๊ฐ ๊ณ ๋ฏผ์ด ์์๋ค.
contentEditable์ HTML์์๋ฅผ ์์ ๊ฐ๋ฅํ๊ฒ ๋ง๋ค์ด์ฃผ๋ ์์ฑ์ด๋ค.
HTML5์ ๋ชจ๋ ์๋ฆฌ๋จผํธ๋ contenteditable='true'
๋ก ์ค์ ํจ์ผ๋ก์จ ํด๋น ์๋ฆฌ๋จผํธ ๋ด๋ถ์ ํ
์คํธ๋ฅผ ์์ฑํ ์ ์๋ค.
contentEditable์์ฑ์ ์ด๊ฑฐํ(enum) ์์ฑ์ธ๋ฐ, true, false ์ด์ธ์๋ inherit์ ๊ฐ์ง ์ ์๋ค. ๊ธฐ๋ณธ(default)์ ๋ถ๋ชจ์์์์ ํธ์ง๊ฐ๋ฅ ์ฌ๋ถ๋ฅผ ์์๋ฐ๋ inherit์ด๋ค.
HTMLElement.isContentEditable
์ ๊ฐ์ด ์ ๊ทผํ์ฌ HTMLElement์ contentEditable ์์ฑ์ ๋ถ๋ฆฌ์ธ(boolean) ๊ฐ์ผ๋ก ๋ฐ์๋ณผ ์ ์๋ค.
์๋ฅผ ๋ค์ด div์ contentEditable ์์ฑ์ true๋ก ์ค์ ํ๋ฉด div ์์๋ input์ฒ๋ผ ์ ๋ ฅ์ ํ ์ ์๊ฒ ๋๋ค. (๋ ธ์ ํ์ด์ง ๋ด๋ถ ํ๋์ ๋ธ๋ก) contentEditable์ ์ฌ์ฉํ๋ ์ด์ ๋ ์น ์๋ํฐ๋ฅผ ์ฝ๊ฒ ๋ง๋ค๊ธฐ ์ํจ์ด๋ค. contentEditable์ ์ฌ์ฉํ๋ฉด ๋ธ๋ผ์ฐ์ ๊ฐ ์์ฒด์ ์ผ๋ก ํด๋ฆฝ๋ณด๋, ๋๋๊ทธ&๋๋กญ, ์คํ ์ทจ์, ์์๊ณผ ๊ฐ์ ๊ธฐ๋ฅ์ ์ ๋ถ ์ ๊ณตํด์ค๋ค.
๋ํ ํน์ ๋ด์ฉ์ ํธ์ง๋ชจ๋๋ก ์์ ํ๊ธฐ ์ฝ๋ค๋ ์ฅ์ ์ด ์๋ค. ํธ์ง ๋ชจ๋ ๋ณ๊ฒฝ์ input์ด๋ textarea๋ก ์์ ํ ํ์๊ฐ ์์ด contentEditable ์์ฑ๋ง ์ถ๊ฐํด์ค๋ ํธ์ง ๋ชจ๋๋ก ๋ณ๊ฒฝํ ์ ์๋ค. ๋๋ฌธ์ div๋ฅผ input์ผ๋ก ๋ฐ๊ฟจ์ ๋ ๋ฐ์ํ ์ ์๋ ์คํ์ผ ๋ณ๊ฒฝ์ ์ ๊ฒฝ์ฐ์ง ์์๋ ๋์ด ํธ๋ฆฌํ๋ค.
contentEditable์ React์์ ์ฌ์ฉํ ๋ ๊ฝค๋ ๋ฌธ์ ๊ฐ ๋ง๋ค. input๊ณผ ๋์ ๋ฐฉ์์ด ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ด๋ค. div ํ๊ทธ์ contentEditable ์์ฑ์ ์ฃผ๋ฉด ์๋์ ๊ฐ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
์ ์๋ฌ๋ suppressContentEditableWarning ์์ฑ์ ์ถ๊ฐํ๋ ๊ฒ์ผ๋ก ํด๊ฒฐ ๊ฐ๋ฅํ๋ค.
div ํ๊ทธ์ contentEditable ์์ฑ์ ์ฃผ๋ฉด ๋จผ์ ์ ๋ ฅ์ change event๊ฐ ์๋๋ผ input event๊ฐ ๋์ํ๋ค. ๋ํ input์ด ์๋๊ธฐ ๋๋ฌธ์ value ๊ฐ์ด ์๋ค. ์ด ๋๋ฌธ์ ์ ์ด ์ปดํฌ๋ํธ์ฒ๋ผ ๋ฆฌ์กํธ๊ฐ DOM์ ์ ์ดํ ์ ์๋ค.
๋ฐ๋ผ์ ๋น์ ์ด ์ปดํฌ๋ํธ ๋ฐฉ์์ผ๋ก contentEditable์ ๊ด๋ฆฌํด์ค์ผํ๋ค.
export const useContentEditable = (initialContent: string) => {
const $contentEditable = useRef<HTMLDivElement>(null);
const [content, _setContent] = useState(initialContent);
const onInput = (event: ChangeEvent<HTMLDivElement>) => {
_setContent(event.target.innerText);
};
const setContent = (newContent: string) => {
if ($contentEditable.current) {
$contentEditable.current.innerText = newContent;
_setContent(newContent);
}
};
useEffect(() => {
setContent(initialContent);
}, []);
return { content, setContent, onInput, $contentEditable };
};
useContentEditable์ DOM์ ์ ๊ทผํ๊ธฐ ์ํ ref์ ์ํ๋ฅผ ์ ์ฅํ๊ธฐ ์ํ useState๋ฅผ ์ถ๊ฐํ๋ค. ์ํ์๋ contentEditable์ innerText๊ฐ ์ ์ฅ๋๋ค.
๋น์ ์ด ์ปดํฌ๋ํธ์ด๊ธฐ ๋๋ฌธ์ ์ํ ๊ฐ์ ์ ์ฅํ ํ์๊ฐ ์๋ค๊ณ ์๊ฐ๋ ์ ์์ง๋ง ์ ๋ ฅ๊ฐ์ ์ฆ๊ฐ์ ์ผ๋ก validationํ๊ธฐ ์ํด ์ถ๊ฐํ๋ค.
contentEitable์ ์๋์ง๋ง element.focus() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ํฌ์ปค์ค๋ฅผ ์ค ์ ์๋ค. ํ์ง๋ง ์ด๋ ๊ฒ ํฌ์ปค์ค๋ฅผ ์ฃผ๊ฒ ๋๋ ๊ฒฝ์ฐ, contentEditable์ ์์ฑ๋ ๋ด์ฉ์ด ์์ด๋ ํฌ์ปค์ค๊ฐ ๋งจ ์์ชฝ์ ์์นํ๋ค. ์ผ๋ฐ์ ์ผ๋ก input์ ํฌ์ปค์ค๋ฅผ ์ฃผ๋ฉด ์์ฑ๋ ๋ด์ฉ์ ๊ฐ์ฅ ๋ค๋ก ๊ฐ๋ ๊ฒ๊ณผ๋ ๋์กฐ์ ์ด๋ค. ๋ฐ๋ผ์ ๋ณ๋์ foucs ํจ์๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํด์ผํ๋ค.
export const focusContentEditableTextToEnd = (element: HTMLElement) => {
if (element.innerText.length === 0) {
element.focus();
return;
}
const selection = window.getSelection();
const newRange = document.createRange();
newRange.selectNodeContents(element);
newRange.collapse(false);
selection?.removeAllRanges();
selection?.addRange(newRange);
};
๋ง์ฝ contentEditable์ innerText์ ๊ธธ์ด๊ฐ 0์ด๋ผ๋ฉด ์์ฑ๋ ๋ด์ฉ์ด ์๋ค๋ ๊ฒ์ด๋ฏ๋ก focus๋ฅผ ์ฌ์ฉํด๋ ๋๋ค. ํ์ง๋ง ์์ฑ๋ ๋ด์ฉ์ด ์๋ ๊ฒฝ์ฐ์๋ ํฌ์ปค์ค๋ฅผ ๊ฐ์ฅ ๋ค์ชฝ์ ๋ถ์ด๊ธฐ ์ํด ํ์ฌ Caret(์ปค์)์ ์์น๋ฅผ ๋ณ๊ฒฝํด์ค์ผํ๋ค. caret์ ์์น๋ฅผ ์ฐพ๊ณ contentEditable์ range(๋๋๊ทธ ๋ ์์ญ)๋ฅผ ๋งจ ๋์ผ๋ก ์ด๋์ํจ๋ค. ๋ง์ง๋ง์ผ๋ก ๊ธฐ์กด range๋ฅผ ์ญ์ ํ๊ณ ์๋ก์ด range๋ฅผ ์ถ๊ฐํจ์ผ๋ก์จ contentEditable ๋ด์ฉ ๋์ ์ปค์๋ฅผ ์์น ์ํฌ ์ ์๋ค.