From 9c510b57b1663c334242eb0a2e1337a5a6ce4361 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Thu, 8 Oct 2026 13:36:18 +0900 Subject: [PATCH] =?UTF-8?q?fix(spreadsheet):=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91=20=EB=92=A4=20=EC=B4=88=EC=A0=90=20=EA=B2=A9?= =?UTF-8?q?=EC=9E=90=EB=A1=9C=20=C2=B7=20=ED=8E=BC=EC=B9=9C=20=ED=95=98?= =?UTF-8?q?=EC=9C=84=20=EB=A9=94=EB=89=B4=EA=B0=80=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=B0=96=EC=9D=B4=EB=A9=B4=20=EC=98=AC=EB=A6=BC(81-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - keepGridFocus — 항목을 누른 뒤 초점이 문서로 빠지면 격자 입력칸으로(Ctrl+Z 등 키가 바로 먹음) - ui_template_context_menu — 하위(▸)를 펼칠 때도 화면 안으로 당김(fit 한 곳) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s --- A00_Common/spreadsheet/spreadsheet_menu.ts | 2 ++ .../spreadsheet/spreadsheet_menu_items.ts | 12 +++++++++++ ui_template/ui_template_context_menu.ts | 21 ++++++++++++------- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/A00_Common/spreadsheet/spreadsheet_menu.ts b/A00_Common/spreadsheet/spreadsheet_menu.ts index a2af5138f..c47a50612 100644 --- a/A00_Common/spreadsheet/spreadsheet_menu.ts +++ b/A00_Common/spreadsheet/spreadsheet_menu.ts @@ -23,6 +23,7 @@ import { colMoreItems, copyCut, copyOnlyItems, + keepGridFocus, pasteItems, rowInsertItems, rowMoreItems, @@ -44,6 +45,7 @@ export function attachMenu(ctx: SpreadsheetContext): PartHandle { const menu = createMapContextMenu("ss"); ctx.root.append(menu.element); + keepGridFocus(ctx, menu.element); // 메뉴 동작 뒤 Ctrl+Z 등 키가 격자로 ctx.cellMenuExtra ??= () => []; // 칸 메뉴 있음 — 조건 만들기가 이 끝에 붙음 function isMerged(range: CellRange): boolean { diff --git a/A00_Common/spreadsheet/spreadsheet_menu_items.ts b/A00_Common/spreadsheet/spreadsheet_menu_items.ts index 649f8ed01..cfb14d7ce 100644 --- a/A00_Common/spreadsheet/spreadsheet_menu_items.ts +++ b/A00_Common/spreadsheet/spreadsheet_menu_items.ts @@ -83,6 +83,18 @@ function back(ctx: SpreadsheetContext): void { (ctx.editor as Editor).focus?.(); } +/** 항목을 누른 뒤 초점이 문서(body)에 떨어졌으면 격자로 — 창을 띄운 동작은 그 창이 초점을 가짐 */ +export function keepGridFocus(ctx: SpreadsheetContext, menuRoot: HTMLElement): void { + menuRoot.addEventListener( + "click", + () => + setTimeout(() => { + if (document.activeElement === document.body) back(ctx); + }), + true, + ); +} + /** 잘라내기 · 복사 — 초점을 격자로 되돌린 뒤 불러야 부품의 copy · cut 듣기가 받음(빈 복사 막음) */ export function copyCut(ctx: SpreadsheetContext, kind: "cut" | "copy"): void { back(ctx); diff --git a/ui_template/ui_template_context_menu.ts b/ui_template/ui_template_context_menu.ts index 3b787d4d3..4edc8545b 100644 --- a/ui_template/ui_template_context_menu.ts +++ b/ui_template/ui_template_context_menu.ts @@ -113,6 +113,18 @@ export function createMapContextMenu(blockClass: string): MapContextMenu { return entry; } + /** 화면 밖으로 나가면 안쪽으로 당김(오른쪽 · 아래 끝) — 열 때 · 하위 메뉴를 펼칠 때 */ + function fit(): void { + const x = parseFloat(element.style.left); + const y = parseFloat(element.style.top); + const box = element.getBoundingClientRect(); + const view = element.ownerDocument.defaultView ?? window; // 새 창에 옮겨진 패널(54-35) + const dx = Math.max(0, box.right - view.innerWidth + 4); + const dy = Math.max(0, box.bottom - view.innerHeight + 4); + if (dx) element.style.left = `${Math.max(x - dx, x - box.left)}px`; + if (dy) element.style.top = `${Math.max(y - dy, y - box.top)}px`; + } + return { element, open(x, y, items) { @@ -152,6 +164,7 @@ export function createMapContextMenu(blockClass: string): MapContextMenu { .forEach((other) => (other.textContent = other.textContent!.replace("▾", "▸"))); body.hidden = !willOpen; head.textContent = `${item.label} ${willOpen ? "▾" : "▸"}`; + fit(); // 펼친 하위가 화면 아래로 넘치면 메뉴를 올림 }); element.append(head, body); }); @@ -160,13 +173,7 @@ export function createMapContextMenu(blockClass: string): MapContextMenu { element.style.left = `${x}px`; element.style.top = `${y}px`; element.hidden = false; - // 화면 밖으로 나가면 안쪽으로 당김(오른쪽 · 아래 끝에서 연 메뉴) - const box = element.getBoundingClientRect(); - const view = element.ownerDocument.defaultView ?? window; // 새 창에 옮겨진 패널(54-35) - const dx = Math.max(0, box.right - view.innerWidth + 4); - const dy = Math.max(0, box.bottom - view.innerHeight + 4); - if (dx) element.style.left = `${Math.max(x - dx, x - box.left)}px`; - if (dy) element.style.top = `${Math.max(y - dy, y - box.top)}px`; + fit(); }, close, contains: (target) => target instanceof Node && element.contains(target),