Files
Aislo/B07_DesignDetail/openwebcad/src/components/CommandLine.tsx
T
eomsangdonandClaude Opus 5.5 ac1124b9f1 fix(B07-CAD): 한글 IME 조합 입력 — 문자 단계 숨은 받개 · 조합 중 단축키 건너뜀
- 문자를 기다리는 단계(TEXT · MTEXT · 표 칸 · 지시선)에서 숨은 input 에 초점 · compositionupdate/end 로 글자 받음
- 조합 중 키(isComposing · 229 · Process)는 단축키 처리에서 건너뜀 · 명령줄도 같음
- 명령 단계는 초점을 풀어 한글 자판이어도 영문 명령 그대로
- 입력 해석 패턴을 input-patterns.ts 로 나눔(700줄 상한)
- 검증 창구 entities 에 label · 시나리오 ui_checks/44-8_CAD_한글입력.js

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
2026-10-05 11:32:37 +09:00

100 lines
3.2 KiB
TypeScript

/** 명령행 — AutoCAD 명령행처럼 기록·입력·자동완성을 담당한다. */
import { type FC, type FormEvent, useMemo, useRef, useState } from 'react';
import { describeCommand, matchCommandPrefixes } from '../commands/registry';
import { getCommandHistory, runCommandInput } from '../commands/run-command';
import { getInputController, getLastStateInstructions } from '../state';
export const CommandLine: FC = () => {
const [text, setText] = useState('');
const [historyIndex, setHistoryIndex] = useState<number | null>(null);
const inputRef = useRef<HTMLInputElement>(null);
const history = getCommandHistory();
const instruction = getLastStateInstructions() || '명령을 입력하거나 도구를 선택하십시오.';
const suggestions = useMemo(() => matchCommandPrefixes(text).slice(0, 8), [text]);
const submit = (event: FormEvent) => {
event.preventDefault();
const value = text.trim();
if (!value) {
// 빈 ENTER는 활성 명령의 확정 신호다 (선택 끝내기·직전 명령 반복)
getInputController().handleEnterKey();
return;
}
// 진행 중인 명령이 문자·숫자를 기다리면 그쪽으로, 아니면 명령으로 해석된다
getInputController().submitText(value);
setText('');
setHistoryIndex(null);
};
const recallHistory = (direction: -1 | 1) => {
if (!history.length) return;
const nextIndex =
historyIndex === null
? history.length - 1
: Math.min(history.length - 1, Math.max(0, historyIndex + direction));
setHistoryIndex(nextIndex);
setText(history[nextIndex].split(' ')[0]);
};
return (
<section className="cad-command-area controls">
<div className="cad-command-history">
{history.slice(-3).map((line, index) => (
// biome-ignore lint/suspicious/noArrayIndexKey: 기록은 순서 자체가 식별자다
<span key={`${line}-${index}`}>{line}</span>
))}
</div>
<form onSubmit={submit}>
<label htmlFor="cad-command">명령:</label>
<input
id="cad-command"
ref={inputRef}
value={text}
onChange={(event) => setText(event.target.value)}
onKeyDown={(event) => {
if (event.nativeEvent.isComposing) return; // 한글 조합 중 키는 IME 몫
if (event.key === 'Escape') {
event.preventDefault();
getInputController().handleEscapeKey();
}
if (event.key === 'ArrowUp') {
event.preventDefault();
recallHistory(-1);
}
if (event.key === 'ArrowDown') {
event.preventDefault();
recallHistory(1);
}
if (event.key === 'Tab' && suggestions.length) {
event.preventDefault();
setText(suggestions[0].id);
}
}}
placeholder="명령 입력 (예: L, LINE, REC, MOVE)"
autoComplete="off"
spellCheck={false}
/>
<span className="cad-command-prompt">{instruction}</span>
</form>
{text.trim() && suggestions.length > 0 && (
<div className="cad-command-suggestions">
{suggestions.map((command) => (
<button
type="button"
key={command.id}
onClick={() => {
runCommandInput(command.id);
setText('');
inputRef.current?.focus();
}}
>
{describeCommand(command)}
</button>
))}
</div>
)}
</section>
);
};