fix(단축키): 전역 Ctrl 단축키 한글 자판 대응 · 공용 판정 함수(214-2)

- A00_Common/key_shortcut.ts 새로 — 영문이면 글자 · 아니면 자판 자리(code) · CAD 와 페이지가 같이 씀
- CAD input-shortcut.ts 는 공용을 다시 내보냄 · tsconfig.app.json 에 공용 파일 포함
- B05 종단 Ctrl+Z · 노선 편집 Ctrl+Z · Y · M02 흐름도 Ctrl+Z · 카드 Ctrl+S · 나무 Ctrl+C · V · 시트 Ctrl+글자
- 시트 키는 한글 조합 중에도 Ctrl+글자 판정
- 새 시험 ctrl-shortcut-hangul · ctrl-shortcut-sheet

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
This commit is contained in:
2026-10-10 15:32:58 +09:00
co-authored by Claude Sonnet 5.5
parent bc2e7a5765
commit f8fff70d56
12 changed files with 182 additions and 35 deletions
+3 -2
View File
@@ -8,6 +8,7 @@
* 고치는 핵심은 `M02_DF_Paper.ts`(판 DOM 을 옮겨 끼움).
* ========================================================================== */
import { isCtrlKey } from "../../A00_Common/key_shortcut";
import { currentLanguageIndex } from "@ui/ui_template_locale";
import { createButton, el } from "@ui/ui_template_elements";
import { openModal } from "@ui/ui_template_modal";
@@ -209,7 +210,7 @@ export function mountFlowPanel(host: HTMLElement, opts: FlowPanelOpts): FlowPane
const root = el("aside", { className: "m02-dfp", children: [grip, open, strip] });
root.addEventListener("keydown", (ev) => {
if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === "z" && !ev.shiftKey) {
if (isCtrlKey(ev, "z") && !ev.shiftKey) {
if ((ev.target as HTMLElement).closest("input, textarea, [contenteditable]")) return;
ev.preventDefault();
paper.undo();
@@ -258,7 +259,7 @@ export function mountFlowPanel(host: HTMLElement, opts: FlowPanelOpts): FlowPane
});
full.addEventListener("keydown", (ev) => {
if (ev.key === "Escape" && !ev.defaultPrevented) shrink();
if ((ev.ctrlKey || ev.metaKey) && ev.key.toLowerCase() === "z" && !ev.shiftKey) {
if (isCtrlKey(ev, "z") && !ev.shiftKey) {
if ((ev.target as HTMLElement).closest("input, textarea, [contenteditable]")) return;
ev.preventDefault();
paper.undo();
@@ -8,6 +8,7 @@
* 접기는 부모의 attachCollapsible(제목 줄 클릭) — 오른쪽 단추를 눌러도 접히지 않음.
* ========================================================================== */
import { isCtrlKey } from "../A00_Common/key_shortcut";
import { createButton, el, showToast } from "@ui/ui_template_elements";
import { loadingError, showLoading } from "@ui/ui_template_loading";
import { StaleError } from "./M02_MasterTemplete_Api_Fetch";
@@ -76,7 +77,7 @@ export function cardShell(opt: CardShellOptions): CardShell {
const host = opt.stateHost ?? opt.body;
let save: (() => void) | null = null;
root.addEventListener("keydown", (event) => {
if (!save || !(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "s") return;
if (!save || !isCtrlKey(event, "s")) return;
event.preventDefault();
save();
});
@@ -10,6 +10,7 @@
* ========================================================================== */
import "./M02_MasterTemplete_TreePanel.css";
import { isCtrlKey } from "../A00_Common/key_shortcut";
import { createButton, el, showConfirmDialog, showToast } from "@ui/ui_template_elements";
import { loadingError, showLoading } from "@ui/ui_template_loading";
import { t as L } from "@ui/ui_template_locale";
@@ -598,7 +599,7 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
body.addEventListener("keydown", (event) => {
const row = (event.target as HTMLElement).closest<HTMLElement>("[data-node]");
if (!row && (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "v") {
if (!row && isCtrlKey(event, "v")) {
event.preventDefault(); // 빈 자리에 초점 = 맨 위에 붙여넣기
return void paste([]);
}
@@ -626,10 +627,10 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
const many = picked.size > 1 && picked.has(row.dataset.node ?? "");
if (many && api.many) void removeMany(pickedTargets());
else if (!(target.kind === "folder" && !target.path.length)) void remove(target);
} else if (target && (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "c") {
} else if (target && isCtrlKey(event, "c")) {
event.preventDefault();
copy(picked.has(row.dataset.node ?? "") ? pickedTargets() : [target]);
} else if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "v") {
} else if (isCtrlKey(event, "v")) {
event.preventDefault(); // 붙일 자리 = 초점 줄이 폴더면 그 안 · 아니면 맨 위
void paste(target?.kind === "folder" ? target.path : []);
}