feat(spreadsheet): D 잇기 — 수식 분석 · 자동 합계 · 상태 표시줄 · 시트 숨김/탭 색(sub3 · sub4)
- spreadsheet_data_menu.ts — 데이터 ▾ 안 「수식 분석」(선행 · 종속 추적 · 화살표 지우기 · 수식 계산) · 오류 ⚠ 부품 · Σ 단추 · 「시트 숨기기 · 탭 색…」 창 - spreadsheet_keys.ts — Alt+= 자동 합계(=SUM(잡은 범위) 로 편집을 열고 Enter 는 사용자) - spreadsheet.ts — 상태 표시줄(합계 · 평균 · 개수 …)을 맨 아래 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6KXAabDTenEU7hrDQmCKY
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
* 키 · 마우스)을 잇음. 한글 조합 = 숨은 textarea 에 늘 초점 · compositionstart 로 편집 시작(keydown 글자 방식 금지).
|
||||
* ⚠ M02 만 `import()` 로 불러옴 — 정적 import 금지(다른 페이지 번들에 안 섞이게).
|
||||
* 저장은 부른 쪽 몫(`onChange` 로 문서 · 자동저장 없음 · [저장] 때 서버가 Node 로 다시 풂).
|
||||
* 화면 차례: 도구 모음(E) · 주소 상자 + 수식 입력줄(D) · 격자(C) · 시트 탭(E).
|
||||
* 화면 차례: 도구 모음(E) · 주소 상자 + 수식 입력줄(D) · 격자(C) · 시트 탭(E) · 상태 표시줄(sub4).
|
||||
* 2단계 부품(찾기 · 메모 · 서식 붓 · 골라 붙여넣기)은 `spreadsheet_extras.ts` 로 이음.
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -24,6 +24,7 @@ import { attachKeys } from "./spreadsheet_keys";
|
||||
import { attachMenu } from "./spreadsheet_menu";
|
||||
import { attachMouse } from "./spreadsheet_mouse";
|
||||
import { LAST_C, LAST_R, selectCell } from "./spreadsheet_selection";
|
||||
import { createStatusBar } from "./spreadsheet_statusbar";
|
||||
import { mountTabs } from "./spreadsheet_tabs";
|
||||
import { mountToolbar } from "./spreadsheet_toolbar";
|
||||
import type {
|
||||
@@ -128,10 +129,12 @@ export function createSpreadsheet(
|
||||
const tabs = mountTabs(ctx);
|
||||
const menu: PartHandle | null = readOnly ? null : attachMenu(ctx);
|
||||
const clip = attachClipboard(ctx);
|
||||
const others = [toolbar, tabs, menu, clip].filter((p): p is PartHandle => !!p);
|
||||
const status = createStatusBar(ctx);
|
||||
const others = [toolbar, tabs, menu, clip, status].filter((p): p is PartHandle => !!p);
|
||||
|
||||
grid.root.classList.add("ss-grid-host");
|
||||
for (const node of [toolbar?.root, ed.bar, grid.root, tabs.root]) if (node) root.append(node);
|
||||
for (const node of [toolbar?.root, ed.bar, grid.root, tabs.root, status.root])
|
||||
if (node) root.append(node);
|
||||
const extras = attachExtras(ctx, ed, toolbar?.root ?? null);
|
||||
others.push(...extras.parts);
|
||||
const detachKeys = attachKeys(ctx, ed, extras.keys);
|
||||
|
||||
@@ -2,18 +2,24 @@
|
||||
* spreadsheet_data_menu.ts (주인 D)
|
||||
* 데이터 단추 · 메뉴 잇기 — 수식 입력줄 앞 [↑] [↓] [필터] [데이터 ▾].
|
||||
* 정렬 · 자동 필터(sub3 `spreadsheet_sort_dialog` · `spreadsheet_filter`) ·
|
||||
* 조건부 서식 · 데이터 유효성 · 이름 관리(sub4 패널 — 부품 오른위 창에 띄움) · 눈금선 보이기/숨기기.
|
||||
* 조건부 서식 · 데이터 유효성 · 이름 관리 · 시트 숨김/탭 색(sub4 패널 — 부품 오른위 창에 띄움) · 눈금선 ·
|
||||
* 수식 분석(sub3 — 선행 · 종속 추적 화살표 · 화살표 지우기 · 수식 계산 · 오류 ⚠ 풍선) · 자동 합계(Σ · Alt+=).
|
||||
* 유효성 목록 칸은 고르면 칸 옆 ▼ · Alt+↓ 로 값 목록(필터 머리 칸이면 필터 목록이 먼저).
|
||||
* ========================================================================== */
|
||||
|
||||
import "./spreadsheet_stage3.css"; // sub4 패널 스타일(잇는 쪽이 붙임)
|
||||
import { createMapContextMenu } from "@ui/ui_template_context_menu";
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import { parseRange, toA1 } from "./spreadsheet_address";
|
||||
import { createMapContextMenu, type MapContextMenuItem } from "@ui/ui_template_context_menu";
|
||||
import { el, showToast } from "@ui/ui_template_elements";
|
||||
import { parseRange, rangeToA1, toA1 } from "./spreadsheet_address";
|
||||
import { autoSumRange } from "./spreadsheet_autosum";
|
||||
import { createCondFmtPanel } from "./spreadsheet_condfmt";
|
||||
import { attachErrorTip } from "./spreadsheet_errcheck";
|
||||
import { openEvalSteps } from "./spreadsheet_eval_steps";
|
||||
import { attachFilter, toggleFilter } from "./spreadsheet_filter";
|
||||
import { createNameManagerPanel } from "./spreadsheet_names";
|
||||
import { createSheetExtraPanel } from "./spreadsheet_sheet_extra";
|
||||
import { openSortDialog, sortSelection } from "./spreadsheet_sort_dialog";
|
||||
import { attachTrace } from "./spreadsheet_trace";
|
||||
import {
|
||||
createValidationDropdown,
|
||||
createValidationPanel,
|
||||
@@ -35,17 +41,32 @@ const TEXT = {
|
||||
grid: "눈금선 보이기 · 숨기기",
|
||||
close: "닫기",
|
||||
list: "값 목록(Alt+↓)",
|
||||
sheets: "시트 숨기기 · 탭 색…",
|
||||
audit: "수식 분석",
|
||||
prec: "선행 참조 추적",
|
||||
dep: "종속 참조 추적",
|
||||
clear: "화살표 지우기",
|
||||
steps: "수식 계산…",
|
||||
noMore: "더 추적할 칸 없음",
|
||||
sum: "자동 합계(Alt+=)",
|
||||
};
|
||||
|
||||
export interface DataMenu {
|
||||
/** 수식 입력줄 앞에 둘 단추들 */
|
||||
buttons: HTMLElement[];
|
||||
keys: Pick<KeyHooks, "filter" | "dropdown">;
|
||||
keys: Pick<KeyHooks, "filter" | "dropdown" | "autosum">;
|
||||
part: PartHandle;
|
||||
}
|
||||
|
||||
export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMenu {
|
||||
const filter = attachFilter(ctx);
|
||||
const trace = attachTrace(ctx);
|
||||
const tip = attachErrorTip(ctx, trace);
|
||||
let closeSteps: (() => void) | null = null;
|
||||
const traced = (more: boolean): void => {
|
||||
if (!more) showToast(TEXT.noMore, "info");
|
||||
back();
|
||||
};
|
||||
const menu = createMapContextMenu("ss");
|
||||
ctx.root.append(menu.element);
|
||||
let closeSort: (() => void) | null = null;
|
||||
@@ -97,13 +118,23 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe
|
||||
|
||||
function openMenu(anchor: HTMLElement): void {
|
||||
const r = anchor.getBoundingClientRect();
|
||||
const items: [string, () => void][] = [
|
||||
const items: MapContextMenuItem[] = [
|
||||
[TEXT.sort, () => ((closeSort = openSortDialog(ctx)), undefined)],
|
||||
[TEXT.filterItem, () => (toggleFilter(ctx), back())],
|
||||
[TEXT.condfmt, () => openPanel(TEXT.condfmt, createCondFmtPanel)],
|
||||
[TEXT.validation, () => openPanel(TEXT.validation, createValidationPanel)],
|
||||
[TEXT.names, () => openPanel(TEXT.names, createNameManagerPanel)],
|
||||
[TEXT.sheets, () => openPanel(TEXT.sheets, createSheetExtraPanel)],
|
||||
[TEXT.grid, () => (toggleGridlines(), back())],
|
||||
{
|
||||
label: TEXT.audit,
|
||||
children: [
|
||||
[TEXT.prec, () => traced(trace.precedents())],
|
||||
[TEXT.dep, () => traced(trace.dependents())],
|
||||
[TEXT.clear, () => (trace.clear(), back())],
|
||||
[TEXT.steps, () => (closeSteps?.(), (closeSteps = openEvalSteps(ctx)))],
|
||||
],
|
||||
},
|
||||
];
|
||||
if (!ctx.readOnly) menu.open(r.left, r.bottom, items);
|
||||
}
|
||||
@@ -161,12 +192,19 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe
|
||||
};
|
||||
document.addEventListener("mousedown", onOutside, true);
|
||||
|
||||
/** 자동 합계 — 엑셀처럼 =SUM(잡은 범위) 로 편집을 열고 Enter 는 사용자 몫 */
|
||||
function autosum(): void {
|
||||
const g = autoSumRange(ctx);
|
||||
ctx.editor.begin(g ? `=SUM(${rangeToA1(g)})` : "=SUM()");
|
||||
}
|
||||
|
||||
const buttons = ctx.readOnly
|
||||
? []
|
||||
: [
|
||||
button("↑", TEXT.asc, () => sortSelection(ctx, false)),
|
||||
button("↓", TEXT.desc, () => sortSelection(ctx, true)),
|
||||
button("필터", TEXT.filter, () => toggleFilter(ctx)),
|
||||
button("Σ", TEXT.sum, autosum, true),
|
||||
button(TEXT.menu, TEXT.menu, openMenu, true),
|
||||
];
|
||||
|
||||
@@ -174,6 +212,7 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe
|
||||
buttons,
|
||||
keys: {
|
||||
filter: () => toggleFilter(ctx),
|
||||
autosum,
|
||||
dropdown() {
|
||||
const f = ctx.sheet().필터;
|
||||
const g = f && parseRange(f.범위);
|
||||
@@ -186,6 +225,8 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe
|
||||
root: null,
|
||||
refresh() {
|
||||
filter.refresh();
|
||||
trace.refresh();
|
||||
tip.refresh();
|
||||
panel?.refresh();
|
||||
closeDrop();
|
||||
const { r, c } = ctx.selection.활성;
|
||||
@@ -203,6 +244,9 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe
|
||||
closePanel();
|
||||
closeDrop();
|
||||
filter.destroy();
|
||||
closeSteps?.();
|
||||
trace.destroy();
|
||||
tip.destroy();
|
||||
menu.element.remove();
|
||||
dialog.remove();
|
||||
arrow.remove();
|
||||
|
||||
@@ -43,6 +43,8 @@ export interface KeyHooks {
|
||||
escape?(): void;
|
||||
/** Ctrl+1 셀 서식 창 */
|
||||
formatDialog?(): void;
|
||||
/** Alt+= 자동 합계 */
|
||||
autosum?(): void;
|
||||
/** Ctrl+Shift+L 필터 켜기 · 끄기 */
|
||||
filter?(): void;
|
||||
/** Alt+↓ — 필터 머리 칸이면 값 목록을 열고 true */
|
||||
@@ -106,6 +108,7 @@ export function attachKeys(ctx: SpreadsheetContext, ed: Editor, hooks: KeyHooks
|
||||
const sel = ctx.selection;
|
||||
const arrow = ARROWS[e.key];
|
||||
if (e.altKey && e.key === "ArrowDown" && hooks.dropdown?.()) return true;
|
||||
if (e.altKey && e.key === "=" && hooks.autosum && !ctx.readOnly) return (hooks.autosum(), true);
|
||||
if (arrow && !e.altKey) {
|
||||
go(move(sheet, sel, arrow[0], arrow[1], { extend: e.shiftKey, jump: ctrl }));
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user