매우 하드한 라이브러리라 기록도 하드하니 주의

회사에서 Telerik 컴포넌트를 사용해보며 특이했던, 어려웠던 케이스를 작성해봅니다.
import { getAttributes, hasAttrs, getAttrs , hole} from "./schema_utils";
export const input = {
    attrs: {
        value: { default: "" },
        style: { default: null },
        contenteditable : {default : true},
        type : {default : null},
        checked : {default : false},
        id : {default : null},
        placeholder : {default : ""}
    },
    group:"block",
    content : "inline*",
    selectable: false,
    parseDOM: [ {
        tag: 'input',
        getAttrs: (dom:any) => ({
            value : dom.getAttribute('value'),
            style: dom.getAttribute('style'),
            contenteditable : dom.getAttribute('contenteditable'),
            type : dom.getAttribute('type'),
            checked : dom.getAttribute('checked'),
            id : dom.getAttribute('id'),
            placeholder : dom.getAttribute('placeholder')
        })
    } ],
    toDOM: (node:any) => {
        return [ 'input', getAttrs(node.attrs) ];
    }
}
위 코드는 예시중 하나인데, input tag 에 해당 노드를 덧붙여서 처리하려고 만든것이다.
보면 attribute 가 들어가는것을 몇몇 항목들에대해 제어하고있다.
let nodes = schema.spec.nodes.addToEnd("select", select);
nodes = nodes.addToEnd("option", option);
nodes = nodes.addToEnd("textarea", textarea);
nodes = nodes.addToEnd("input", input);
적용은 위처럼 하면된다.
그럼이제 한글 입력 이슈를 처리해보자. 아래는 전체 코드
new EditorView({
            mount : event.dom
        },
        {
            ...event.viewProps,
            state : EditorState.create({ doc : doc, plugins }),
            handleDOMEvents: {
                // https://prosemirror.net/docs/ref/#view.EditorProps.handleDOMEvents
                ...event.viewProps.handleDOMEvents,
                keydown: (_view : any, event:any) => {
                    const { code, target, ctrlKey } = event;
                    return (
                    (code === "Backspace" ||
                        code === "Delete" ||
                        (ctrlKey && code === "KeyA")) &&
                    target.nodeName === "INPUT"
                    );
                },
                input: (view : any, event:any) => {
                    const target = event.target;
                    try{
                        if (!event.isComposing && target.nodeName === "INPUT") {
                            const cursor = target.selectionStart;
                            const parent = target.parentNode;
                            const index = Array.from(parent.childNodes).indexOf(target);
                            const pos = view.posAtDOM(target);
                            var tr;
                            if(props.editdisabled){
                                return;
                            }
                            tr = view.state.tr.setNodeMarkup(pos-1, null, {
                                value: target.value,
                                type: target.getAttribute("type")
                            });
                            view.dispatch(tr);
                            const input = parent.childNodes.item(index);
                            input.focus();
                            input.setSelectionRange(cursor, cursor);
                        } else if(target.nodeName === "SELECT"){
                            const pos = view.posAtDOM(target);
                            let tr = view.state.tr.setNodeMarkup(pos-1, null, {
                                value : target.value,
                                id : target.id
                            });
                            for(var i=0;i<target.childNodes.length;i++){
                                const pos = view.posAtDOM(target.childNodes[i]);
                                target.childNodes[i].innerText
                                if(target.childNodes[i].value == target.value){
                                    tr = tr.setNodeMarkup(pos-1, null, {
                                        selected : "selected",
                                        value : target.childNodes[i].value
                                    });
                                } else {
                                    tr = tr.setNodeMarkup(pos-1, null, {
                                        value : target.childNodes[i].value
                                    });
                                }
                            }
                            view.dispatch(tr);
                        }
                    }catch(err){console.log(err)}
                }
            }
        });
위코드에서 말하는건 단순한데,
에디터를 만들때 이벤트 핸들링 하는 함수에 추가적인 액션을 넣는다.
한글은 영어와 다르게 하나의 커서안에서 최대 4개의 키를 입력할수도 있는데,
이게 prose-mirror 에디터에는 적용이 안되어있다. 
ex: hello -> 5커서, 안녕 -> 2커서, 6번의 타이핑
// <- 현재 타이핑중이라면
event.iscomposing  
// <- editor에서 클릭하여 커서가있는 타겟이 들어온다 이게 input 이라면
target.nodeName == "INPUT" 
// 1. 매번 입력시마다 이전 커서 위치를 가져온다. (입력시마다 커서는 한칸씩 이동된다)
// 2. 현재 커서 위치를 이전 커서위치로 강제로 focus 한다.
const cursor = target.selectionStart;
const parent = target.parentNode;
const index = Array.from(parent.childNodes).indexOf(target);
const pos = view.posAtDOM(target);
var tr;
if(props.editdisabled){
	return;
}
tr = view.state.tr.setNodeMarkup(pos-1, null, {
	value: target.value,
	type: target.getAttribute("type")
	});
view.dispatch(tr);
const input = parent.childNodes.item(index);
input.focus();
input.setSelectionRange(cursor, cursor);
custom cell은 아래와같이 적용하면된다.
그리드 컴포넌트.tsx 예시
<GridContainer .... >
<Column
	key={idx}
	headerClassName={headerClass[idx]}
	className={rowClass[idx]}
	field={raw}
	title={props.titles[idx]}
	filterable={false}
	width={
		keysWidth && keysWidth[idx]
			? keysWidth[idx]
			: undefined
	}
	// minResizableWidth={64}
	cell={(e) => {
		if (props.getCustomEl)
			return props.getCustomEl(
				idx,
				e.dataIndex,
				e.columnIndex,
				e.dataItem,
				e
			);
		return null;
	}}
	locked={isLocked}
/>
</GridContainer>
props.getCustomEl
const getCustomEl = (
        idx: number,
        dataIdx?: number,
        columnIdx?: number,
        dataItem?: any,
        props?: GridCellProps
    ) => {
    	if (dataIdx == undefined) {
	        // 해당 컬럼이 custom cell 로 지정된 컬럼이라면 true
            if (customElIndexes.indexOf(idx) != -1) return true; 
            return false;
        }
        // dataIdx 가 undefiend 가 아닐경우 custom cell return
        return <S.TableTd ...blahblah > </S.TableTd>;
    }
우선 getCustomEl이라는 함수를 만든 이유는
GridComponent <-> MainPage 와같이 데이터가 있는 페이지와 컴포넌트사이에서 전달하기 위함이다.
핵심은 dataIdx 가 없어도 해당 컬럼이 custom cell이라면 true라도 날려줘야하는데,
이유는 한번 false라고 날려준 컬럼은 나중에 데이터가 적용되서 제대로된 엘리먼트를 리턴하더라도
출력이안된다.
(애초에 dataIdx에 undefined 들어오는게 너무웃김..ㅋㅋㅋ)
이외에도 참 어이없는것들이 많은데 추후에 추가해볼 예정.
라이브러리가 잘 만들었으나, develop 이 많이 필요하다.
이럴거면 그냥 material-ui 쓰지.. 괜히 도입해서 개고생이다.