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
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 한글 IME 받개 — 캔버스에는 글 칸이 없어 조합(compositionstart/update/end)이 생기지 않는다.
|
||||
* 문자를 기다리는 단계(TEXT · MTEXT · 표 칸 · 지시선 …)에서만 숨은 input 에 초점을 줘 조합을 받고,
|
||||
* 끝난 글자를 넘긴다. 명령 단계에서는 초점을 풀어 한글 자판이어도 L · REC 같은 영문 명령이 들어간다.
|
||||
*/
|
||||
import { getActiveToolActor } from '../state.ts';
|
||||
|
||||
/** 조합 중 키 — 단축키·글자 처리가 먹지 않고 IME 에 넘긴다 */
|
||||
export const isImeKey = (evt: KeyboardEvent): boolean =>
|
||||
evt.isComposing || evt.keyCode === 229 || evt.key === 'Process';
|
||||
|
||||
/** 활성 도구의 지금 단계가 TEXT_INPUT 을 받는가 */
|
||||
export function activeToolAcceptsText(): boolean {
|
||||
const snapshot = getActiveToolActor()?.getSnapshot();
|
||||
return !!snapshot?.machine?.states?.[snapshot?.value]?.config?.on?.TEXT_INPUT;
|
||||
}
|
||||
|
||||
export class ImeSink {
|
||||
/** 조합 중인 글자 — 커서 옆 입력 칸에 미리 보인다 */
|
||||
public composing = '';
|
||||
public element: HTMLInputElement | null = null;
|
||||
|
||||
constructor(private readonly onCommit: (text: string) => void) {}
|
||||
|
||||
public attach() {
|
||||
const el = document.createElement('input');
|
||||
el.className = 'cad-ime-sink';
|
||||
el.tabIndex = -1;
|
||||
el.autocomplete = 'off';
|
||||
el.setAttribute('aria-hidden', 'true');
|
||||
el.style.cssText =
|
||||
'position:fixed;left:0;top:0;width:1px;height:1px;opacity:0;border:0;padding:0;pointer-events:none';
|
||||
el.addEventListener('compositionupdate', (evt) => this.handleCompositionUpdate(evt.data));
|
||||
el.addEventListener('compositionend', (evt) => this.handleCompositionEnd(evt.data));
|
||||
el.addEventListener('input', (evt) => this.handleInput(evt as InputEvent));
|
||||
document.body.appendChild(el);
|
||||
this.element = el;
|
||||
}
|
||||
|
||||
public handleCompositionUpdate(data: string) {
|
||||
this.composing = data ?? '';
|
||||
}
|
||||
|
||||
public handleCompositionEnd(data: string) {
|
||||
this.composing = '';
|
||||
if (this.element) this.element.value = '';
|
||||
if (data) this.onCommit(data);
|
||||
}
|
||||
|
||||
/** 조합 직후 IME 가 바로 넣는 글자(띄어쓰기 · 숫자) — 조합 중 input 은 compositionend 가 맡는다 */
|
||||
public handleInput(evt: Pick<InputEvent, 'isComposing' | 'data'>) {
|
||||
if (evt.isComposing || !evt.data) return;
|
||||
if (this.element) this.element.value = '';
|
||||
this.onCommit(evt.data);
|
||||
}
|
||||
|
||||
/** 문자를 기다리면 받개에 초점 · 아니면 놓음. 다른 입력 칸(명령줄 · 대화상자)의 초점은 뺏지 않는다 */
|
||||
public sync(wantsText: boolean) {
|
||||
const el = this.element;
|
||||
if (!el) return;
|
||||
const active = document.activeElement;
|
||||
if (wantsText && !active?.closest('input, textarea, select, [contenteditable]')) {
|
||||
el.focus({ preventScroll: true });
|
||||
} else if (!wantsText && active === el && !this.composing) {
|
||||
el.blur();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user