|
1 | | -import { useRef } from 'react'; |
| 1 | +import React, { useRef } from 'react'; |
2 | 2 |
|
3 | | -export function useOffset() { |
| 3 | +export function useOffset(blockRef: React.RefObject<HTMLParagraphElement>) { |
4 | 4 | const offsetRef = useRef<number | null>(null); |
5 | 5 |
|
6 | | - const setOffset = () => { |
| 6 | + const setOffset = (offset = 0) => { |
| 7 | + if (!blockRef.current) return; |
| 8 | + |
7 | 9 | const selection = window.getSelection(); |
8 | 10 |
|
9 | 11 | if (selection?.rangeCount) { |
10 | 12 | const range = selection.getRangeAt(0); |
11 | 13 |
|
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)); |
13 | 24 | } |
14 | 25 | }; |
15 | 26 |
|
16 | 27 | const clearOffset = () => { |
17 | 28 | offsetRef.current = null; |
18 | 29 | }; |
19 | 30 |
|
| 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 | + // 위 아래 방향키 이동은 핸들링하지 않음 |
20 | 47 | const onKeyUp: React.KeyboardEventHandler = (e) => { |
21 | | - const arrowKeys = ['ArrowRight', 'ArrowLeft', 'ArrowDown', 'ArrowUp']; |
| 48 | + const ARROW_DOWN = 'ArrowDown'; |
| 49 | + const ARROW_UP = 'ArrowUp'; |
22 | 50 |
|
23 | | - if (arrowKeys.includes(e.nativeEvent.key)) { |
| 51 | + if ([ARROW_DOWN, ARROW_UP].includes(e.nativeEvent.key)) { |
24 | 52 | setOffset(); |
25 | 53 | } |
26 | 54 | }; |
27 | 55 |
|
28 | 56 | 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>, |
31 | 61 | onBlur: clearOffset, |
32 | | - onKeyUp: onKeyUp, |
| 62 | + onKeyDown, |
| 63 | + onKeyUp, |
33 | 64 | }; |
34 | 65 |
|
35 | 66 | return { |
|
0 commit comments