diff --git a/A00_Common/spreadsheet/spreadsheet.ts b/A00_Common/spreadsheet/spreadsheet.ts index 27ac5da6a..6d7fefd78 100644 --- a/A00_Common/spreadsheet/spreadsheet.ts +++ b/A00_Common/spreadsheet/spreadsheet.ts @@ -28,7 +28,7 @@ import type { LinkOptions, Links } from "./spreadsheet_links"; import { linkCommand, type LinkInfo } from "./spreadsheet_link_set"; import { attachMenu } from "./spreadsheet_menu"; import { attachMouse } from "./spreadsheet_mouse"; -import { LAST_C, LAST_R, selectCell } from "./spreadsheet_selection"; +import { LAST_C, LAST_R, selectCell, selectRanges } from "./spreadsheet_selection"; import { createStatusBar } from "./spreadsheet_statusbar"; import { mountTabs } from "./spreadsheet_tabs"; import { mountToolbar } from "./spreadsheet_toolbar"; @@ -90,6 +90,9 @@ export interface SpreadsheetHandle { /** HTML 표를 지금 시트 고른 칸(왼위)부터 붙임(105-1 도면 → 면적) — 붙여넣기와 같은 길(병합 · 되돌리기 한 번) · * `check` = 덮을 자리의 값 칸 · 알약 칸 수 · 범위 → false 면 안 붙임 · 붙인 범위 A1 · 읽기만 · 못 하면 null */ pasteTable(html: string, check?: PasteCheck): Promise; + /** 범위 고름 · 보이게 굴림(329-2 엑셀 보기 창) — `시트` = id 나 이름(없으면 지금 시트 · 다르면 그 탭으로) · + * 범위 여럿 = 여러 고름 · 시트가 없거나 주소가 틀리면 false(고름 그대로) */ + select(범위: string | string[], 시트?: string): boolean; destroy(): void; } @@ -350,6 +353,18 @@ export function createSpreadsheet( return 이름; }, pasteTable: (html, check) => pasteTableAt(ctx, html, check), + select(range, sheetKey) { + const target = sheetKey + ? ctx.book.시트.find((s) => s.id === sheetKey || s.이름 === sheetKey) + : ctx.sheet(); + const next = target && selectRanges(target, typeof range === "string" ? [range] : range); + if (!target || !next) return false; + showSheet(target.id); + if (ctx.selection.시트 !== target.id) return false; // 편집기 유효성에 막힘 + select(next, true); + grid.reveal(next.활성.r, next.활성.c); // 범위가 판보다 크면 왼위가 보이게 + return true; + }, destroy() { clearTimeout(toastTimer); detachKeys(); diff --git a/A00_Common/spreadsheet/spreadsheet_selection.ts b/A00_Common/spreadsheet/spreadsheet_selection.ts index 46c0c3e8c..750318511 100644 --- a/A00_Common/spreadsheet/spreadsheet_selection.ts +++ b/A00_Common/spreadsheet/spreadsheet_selection.ts @@ -91,6 +91,20 @@ export function selectSpan(sheet: Sheet, anchor: CellAddress, to: CellAddress): return { 시트: sheet.id, 범위: [g], 활성: { ...anchor }, 기준: { ...anchor } }; } +/** A1 범위 글 여럿 → 고름(329-2 손잡이 `select`) — 병합만큼 늘림 · 활성 = 첫 범위 왼위 · 틀린 글 · 빈 목록 = null */ +export function selectRanges(sheet: Sheet, texts: string[]): Selection | null { + const list = merges(sheet); + const 범위: CellRange[] = []; + for (const t of texts) { + const g = parseRange(t); + if (!g) return null; + 범위.push(expand(g, list)); + } + if (!범위.length) return null; + const at = { r: 범위[0].r0, c: 범위[0].c0 }; + return { 시트: sheet.id, 범위, 활성: at, 기준: { ...at } }; +} + /** 행 전체(r0~r1) · 열 전체(c0~c1) · 전체 — 활성은 첫 보이는 칸 · 걸친 병합만큼 늘림(엑셀) */ export function selectRows(sheet: Sheet, a: number, b: number, active?: number): Selection { const r0 = Math.min(a, b); diff --git a/resources/tester/spreadsheet/helper_spreadsheet_d.cjs b/resources/tester/spreadsheet/helper_spreadsheet_d.cjs index e93264998..792c7c4a1 100644 --- a/resources/tester/spreadsheet/helper_spreadsheet_d.cjs +++ b/resources/tester/spreadsheet/helper_spreadsheet_d.cjs @@ -65,6 +65,11 @@ const out = { })(), cycleSingle: sel.cycle(s1, cell(3, 2), 1, 0), usedEnd: addr.toA1(sel.usedEnd(s1).r, sel.usedEnd(s1).c), + // 손잡이 select(범위) — 범위 글 → 고름(병합 늘림 · 여럿 · 틀린 글 null) + ranges: [["B20:O24"], ["D4"], ["B20:O24", "A1:A2"], ["가1"], []].map((t) => { + const s = sel.selectRanges(s1, t); + return s && [addr.toA1(s.활성.r, s.활성.c), ...s.범위.map(addr.rangeToA1)].join("|"); + }), // 입력 글 → 칸 textToCell: ["12", "1,234.5", "50%", "-1e3", "true", "'=12", "=", "=A1+1", "", "1,23", "abc"].map( (t) => ed.textToCell(t, { 서식: 4 }), diff --git a/resources/tester/spreadsheet/test_spreadsheet_d_input.py b/resources/tester/spreadsheet/test_spreadsheet_d_input.py index ebd4f575b..4f8252708 100644 --- a/resources/tester/spreadsheet/test_spreadsheet_d_input.py +++ b/resources/tester/spreadsheet/test_spreadsheet_d_input.py @@ -46,6 +46,11 @@ def test_cycle_in_selection(r): assert r["cycleSingle"] is None +def test_select_ranges(r): + # 엑셀 보기 창(329-2) 호표 영역 강조 — 활성 = 왼위 · 병합 C4:F4 · A1:H1 걸치면 늘림 · 틀린 글 · 빈 목록 null + assert r["ranges"] == ["B20|B20:O24", "C4|C4:F4", "B20|B20:O24|A1:H2", None, None] + + def test_text_to_cell(r): got = r["textToCell"] assert [c.get("값", c.get("식")) for c in got] == [