diff --git a/A00_Common/spreadsheet/spreadsheet.ts b/A00_Common/spreadsheet/spreadsheet.ts index 1597924a..f53be2df 100644 --- a/A00_Common/spreadsheet/spreadsheet.ts +++ b/A00_Common/spreadsheet/spreadsheet.ts @@ -244,6 +244,7 @@ export function createSpreadsheet( recalc() { ctx.engine.rebuild(ctx.book); grid.render(); + for (const p of others) p.refresh(); return ctx.engine.snapshot(); }, destroy() { diff --git a/A00_Common/spreadsheet/spreadsheet_extras.ts b/A00_Common/spreadsheet/spreadsheet_extras.ts index bcb24ea3..47bc79f1 100644 --- a/A00_Common/spreadsheet/spreadsheet_extras.ts +++ b/A00_Common/spreadsheet/spreadsheet_extras.ts @@ -2,19 +2,22 @@ * spreadsheet_extras.ts (주인 D) * 2단계 부품 잇기(sub1 파일들의 「잇는 법」) — 찾기 패널 · 메모 풍선(마우스 올림) · 메모 편집(Shift+F2) · * 서식 붓(수식 입력줄 앞 단추 · 두 번 누르면 계속 · Esc 끔) · 골라 붙여넣기(Ctrl+Shift+V → 값만 · 서식만 · 수식만) · - * 도구 모음을 누른 뒤 초점을 격자로 되돌림. + * 도구 모음을 누른 뒤 초점을 격자로 되돌림 · 정렬(↑ ↓ 정렬…) · 자동 필터(단추 · Ctrl+Shift+L · Alt+↓). * 빨간 세모는 C 격자 · 우클릭 [메모 …] 는 E 메뉴 몫(브레인 배정). * ========================================================================== */ import { el } from "@ui/ui_template_elements"; +import { parseRange } from "./spreadsheet_address"; import { parseClipboard } from "./spreadsheet_clipboard"; import { attachComments, getComment, setComment } from "./spreadsheet_comments"; import type { Editor } from "./spreadsheet_editor"; +import { attachFilter, toggleFilter } from "./spreadsheet_filter"; import { mountFindPanel } from "./spreadsheet_find_panel"; import { createFormatPainter } from "./spreadsheet_format_painter"; import type { KeyHooks } from "./spreadsheet_keys"; import type { MouseHooks } from "./spreadsheet_mouse"; import { pasteSpecial, type PasteSpecialMode } from "./spreadsheet_paste_special"; +import { openSortDialog, sortSelection } from "./spreadsheet_sort_dialog"; import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types"; const TEXT = { @@ -22,6 +25,10 @@ const TEXT = { pasteTitle: "골라 붙여넣기", modes: { 값: "값만", 서식: "서식만", 수식: "수식만" } as Record, memo: "메모 — Ctrl+Enter 또는 밖을 누르면 적용 · Esc 취소", + sortAsc: "오름차순 정렬(활성 칸 열 기준)", + sortDesc: "내림차순 정렬(활성 칸 열 기준)", + sortDialog: "정렬 — 기준 여럿 · 머리 행", + filter: "자동 필터 켜기 · 끄기(Ctrl+Shift+L · 머리 칸에서 Alt+↓ 값 목록)", }; export interface Extras { @@ -108,6 +115,33 @@ export function attachExtras( }); if (!ctx.readOnly) ed.bar.prepend(brush); + // ── 정렬 · 필터(sub3) — 수식 입력줄 앞 데이터 단추 ─────────────────────── + const filter = attachFilter(ctx); + let closeSort: (() => void) | null = null; + const dataButton = (text: string, title: string, run: () => void, dialog = false) => { + const b = el("button", { className: "ss-brush", text, attrs: { type: "button", title } }); + b.addEventListener("click", () => { + run(); + if (!dialog) back(); + }); + return b; + }; + const data = [ + dataButton("↑", TEXT.sortAsc, () => sortSelection(ctx, false)), + dataButton("↓", TEXT.sortDesc, () => sortSelection(ctx, true)), + dataButton( + "정렬…", + TEXT.sortDialog, + () => { + closeSort?.(); + closeSort = openSortDialog(ctx); + }, + true, + ), + dataButton("필터", TEXT.filter, () => toggleFilter(ctx)), + ]; + if (!ctx.readOnly) brush.after(...data); + // ── 골라 붙여넣기 ───────────────────────────────────────────────────────── let armed = false; const picker = el("div", { className: "ss-pick", attrs: { hidden: "", title: TEXT.pasteTitle } }); @@ -167,6 +201,8 @@ export function attachExtras( root: null, refresh: () => showBrush(), destroy() { + closeSort?.(); + data.forEach((b) => b.remove()); ctx.root.removeEventListener("paste", onPaste, true); toolbarRoot?.removeEventListener("click", onToolbarClick); toolbarRoot?.removeEventListener("change", back); @@ -181,6 +217,15 @@ export function attachExtras( find: (replace) => find.open(replace), pasteSpecial: () => (armed = true), comment: editMemo, + filter: () => toggleFilter(ctx), + dropdown() { + const f = ctx.sheet().필터; + const g = f && parseRange(f.범위); + const { r, c } = ctx.selection.활성; + if (!g || r !== g.r0 || c < g.c0 || c > g.c1) return false; + filter.open(c); + return true; + }, escape() { painter.clear(); sticky = false; @@ -203,6 +248,6 @@ export function attachExtras( put(comments.root!, hit.r, hit.c); // showBalloon 의 cellBox 자리 → 화면 자리로 고침 }, }, - parts: [find, comments, self], + parts: [find, comments, filter, self], }; } diff --git a/A00_Common/spreadsheet/spreadsheet_keys.ts b/A00_Common/spreadsheet/spreadsheet_keys.ts index 22315c3a..a23180e1 100644 --- a/A00_Common/spreadsheet/spreadsheet_keys.ts +++ b/A00_Common/spreadsheet/spreadsheet_keys.ts @@ -41,6 +41,10 @@ export interface KeyHooks { comment?(): void; /** 고름 중 Esc(서식 붓 끄기 등) */ escape?(): void; + /** Ctrl+Shift+L 필터 켜기 · 끄기 */ + filter?(): void; + /** Alt+↓ — 필터 머리 칸이면 값 목록을 열고 true */ + dropdown?(): boolean; } export function attachKeys(ctx: SpreadsheetContext, ed: Editor, hooks: KeyHooks = {}): () => void { @@ -97,6 +101,7 @@ export function attachKeys(ctx: SpreadsheetContext, ed: Editor, hooks: KeyHooks const sheet = ctx.sheet(); const sel = ctx.selection; const arrow = ARROWS[e.key]; + if (e.altKey && e.key === "ArrowDown" && hooks.dropdown?.()) return true; if (arrow && !e.altKey) { go(move(sheet, sel, arrow[0], arrow[1], { extend: e.shiftKey, jump: ctrl })); return true; @@ -135,6 +140,10 @@ export function attachKeys(ctx: SpreadsheetContext, ed: Editor, hooks: KeyHooks if (!hooks.find) return false; hooks.find(k === "h"); return true; + case "l": + if (!e.shiftKey || !hooks.filter) return false; + hooks.filter(); + return true; case "v": if (e.shiftKey) hooks.pasteSpecial?.(); return false; // paste 사건은 그대로 — E 클립보드 · 골라 붙여넣기가 받음 diff --git a/A00_Common/spreadsheet/spreadsheet_selection.ts b/A00_Common/spreadsheet/spreadsheet_selection.ts index 64449dcf..779324dc 100644 --- a/A00_Common/spreadsheet/spreadsheet_selection.ts +++ b/A00_Common/spreadsheet/spreadsheet_selection.ts @@ -51,7 +51,11 @@ export function mergeAt(sheet: Sheet, r: number, c: number): CellRange { return merges(sheet).find((m) => inRange(m, r, c)) ?? { r0: r, c0: c, r1: r, c1: c }; } -export const rowHidden = (sheet: Sheet, r: number): boolean => !!sheet.행?.[String(r + 1)]?.숨김; +/** 숨긴 행 · 필터로 걸러진 행(방향키 · Enter 가 건너뜀 — 엑셀) */ +export function rowHidden(sheet: Sheet, r: number): boolean { + const row = sheet.행?.[String(r + 1)]; + return !!(row?.숨김 || row?.걸러짐); +} export function colHidden(sheet: Sheet, c: number): boolean { const cols = sheet.열;