Skip to content

Commit 9e58b2c

Browse files
authored
Fix/#226-K: 블럭 내부 좌우 방향키 커서 핸들러 개선 (#228)
* fix: 좌우 방향키 onKeyDown 핸들러 추가 - 예외 케이스 고려하지 않은 좌우 방향키 onKeyDown 핸들러 추가 - Block에서 기존에 사용하던 onKeyDown도 함께 실행될 수 있도록 함 * feat: 0보다 작은 offset 핸들링 * feat: 현재 노드의 범위를 벗어나는 offset 핸들링 * fix: eventHandler 타입 명시
1 parent 043872c commit 9e58b2c

File tree

2 files changed

+51
-12
lines changed

2 files changed

+51
-12
lines changed

client/src/components/Mom/Block/index.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ function Block({ id, onKeyDown, index }: BlockProps) {
2828
remoteDeleteCRDT,
2929
} = useCRDT();
3030

31-
const { offsetRef, setOffset, clearOffset, offsetHandlers } = useOffset();
32-
3331
const blockRef = useRef<HTMLParagraphElement>(null);
3432

33+
const { offsetRef, setOffset, clearOffset, offsetHandlers } =
34+
useOffset(blockRef);
35+
3536
// 로컬에서 일어나는 작성 - 삽입과 삭제 연산
3637
const onInput: React.FormEventHandler = (e) => {
3738
setOffset();
@@ -159,6 +160,13 @@ function Block({ id, onKeyDown, index }: BlockProps) {
159160
e.preventDefault();
160161
};
161162

163+
const onKeyDownComposite: React.KeyboardEventHandler<HTMLParagraphElement> = (
164+
e,
165+
) => {
166+
offsetHandlers.onKeyDown(e);
167+
onKeyDown(e);
168+
};
169+
162170
return (
163171
<p
164172
ref={blockRef}
@@ -167,7 +175,7 @@ function Block({ id, onKeyDown, index }: BlockProps) {
167175
onInput={onInput}
168176
onCompositionEnd={onCompositionEnd}
169177
{...offsetHandlers}
170-
onKeyDown={onKeyDown}
178+
onKeyDown={onKeyDownComposite}
171179
onPaste={onPaste}
172180
suppressContentEditableWarning={true}
173181
>

client/src/hooks/useOffset.tsx

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,66 @@
1-
import { useRef } from 'react';
1+
import React, { useRef } from 'react';
22

3-
export function useOffset() {
3+
export function useOffset(blockRef: React.RefObject<HTMLParagraphElement>) {
44
const offsetRef = useRef<number | null>(null);
55

6-
const setOffset = () => {
6+
const setOffset = (offset = 0) => {
7+
if (!blockRef.current) return;
8+
79
const selection = window.getSelection();
810

911
if (selection?.rangeCount) {
1012
const range = selection.getRangeAt(0);
1113

12-
offsetRef.current = range.startOffset;
14+
const preCaretRange = range.cloneRange();
15+
preCaretRange.selectNodeContents(
16+
blockRef.current as HTMLParagraphElement,
17+
);
18+
preCaretRange.setEnd(range.endContainer, range.endOffset);
19+
const maxOffset = preCaretRange.toString().length;
20+
21+
const nextOffset = range.startOffset + offset;
22+
23+
offsetRef.current = Math.min(maxOffset, Math.max(0, nextOffset));
1324
}
1425
};
1526

1627
const clearOffset = () => {
1728
offsetRef.current = null;
1829
};
1930

31+
// keydown 이벤트는 키 입력의 내용 반영 이전에 발생
32+
const onKeyDown: React.KeyboardEventHandler = (e) => {
33+
const ARROW_LEFT = 'ArrowLeft';
34+
const ARROW_RIGHT = 'ArrowRight';
35+
36+
switch (e.nativeEvent.key) {
37+
case ARROW_LEFT:
38+
setOffset(-1);
39+
return;
40+
case ARROW_RIGHT:
41+
setOffset(1);
42+
return;
43+
}
44+
};
45+
46+
// 위 아래 방향키 이동은 핸들링하지 않음
2047
const onKeyUp: React.KeyboardEventHandler = (e) => {
21-
const arrowKeys = ['ArrowRight', 'ArrowLeft', 'ArrowDown', 'ArrowUp'];
48+
const ARROW_DOWN = 'ArrowDown';
49+
const ARROW_UP = 'ArrowUp';
2250

23-
if (arrowKeys.includes(e.nativeEvent.key)) {
51+
if ([ARROW_DOWN, ARROW_UP].includes(e.nativeEvent.key)) {
2452
setOffset();
2553
}
2654
};
2755

2856
const offsetHandlers = {
29-
onFocus: setOffset,
30-
onClick: setOffset,
57+
onFocus:
58+
setOffset as unknown as React.FocusEventHandler<HTMLParagraphElement>,
59+
onClick:
60+
setOffset as unknown as React.MouseEventHandler<HTMLParagraphElement>,
3161
onBlur: clearOffset,
32-
onKeyUp: onKeyUp,
62+
onKeyDown,
63+
onKeyUp,
3364
};
3465

3566
return {

0 commit comments

Comments
 (0)