M02 면적산출 SA01010100 에서 도구 막대 · 데이터 메뉴 · 우클릭 · 셀 서식 여섯 탭 · 찾기 · 메모 · 이름 관리 · 조건부 서식 · 유효성 · 이동 · 틀 고정 · 칸 넣기/지우기 · 데이터 도구 · 윤곽 · 수식 분석 · 되돌리기/다시 · 저장 안 하고 떠나기 · 다시 열기를 빈 값 · 같은 이름 · 긴 이름 · 취소 · Esc · Enter · 밝은/어두운 테마 예외까지 돌려 찾은 흠. - 격자(C): 틀고정이 있으면 본문 판이 그릴 행열을 틀고정 폭만큼 덜 셈 — 아래 · 오른쪽 끝이 빈 칸으로 남던 것(29 행 고정 때 1 행만 그림) · visibleRange 도 같음 · reveal 은 바로 그림 - 도구 막대: 세로 정렬 기본 표시 「위」 → 「아래」(칸 실제와 맞춤) · 크기 409 넘으면 409 · 테두리 · 단위 판이 단추 위로 떠 머리까지 덮던 것 → 단추 아래 · ✕ · 수식 안내 · 확대 글 대비(흐림 투명도 없앰 · --color-text-secondary · 밝은 5.88 · 어두운 7.91) - 병합(도구 막대 · 우클릭): 왼위 밖 값이 말없이 지워지던 것 → 엑셀처럼 먼저 물음(confirmMerge) - 셀 서식 · 이동 · 칸 넣기/지우기 · 텍스트 나누기 · 중복 제거 · 연속 채우기 · 메모 창: 열 때 초점이 창 밖(BODY)이라 Esc · Enter 가 안 먹던 것 → dialogKeys(창 안 초점 · Esc 닫기 · Enter 실행) · 셀 서식 창 탭마다 높이가 널뛰던 것 → 본문 최소 높이 - 우클릭 메뉴(ui_template 공용): Esc 로 닫힘 · 화면 밖이면 안쪽으로 당김 · 데이터 ▾ 메뉴와 알약 우클릭 메뉴가 화면 좌표를 부품 기준 자리로 써 오른쪽 아래로 떨어지던 것 고침 · 행 · 열 머리 메뉴에 잘라내기 · 붙여넣기(안내) 더함 - 자동 필터 ▼ 가 칸 밑에 깔려 눌리지 않던 것(z-index) · 값 목록이 격자 아래 끝에서 잘리면 위로 뒤집음 - Σ: 여러 칸을 고르면 첫 칸에 =SUM() 편집을 열어 Enter 로 값을 덮던 것 → 엑셀처럼 열마다 바로 아래(또는 고름 끝 빈 칸)에 합계를 바로 넣음(autoSumCells) - 조건부 서식: 빈 값이 「보다큼 0」 규칙이 되던 것 · #RRGGBB 아닌 색 · 색 없는 규칙 → 안내 후 막음 - 이름 관리: 255 자 넘는 이름 막음 · 긴 이름에 창이 화면 끝까지 늘던 것 · 시트 id(s1) 대신 시트 이름 · 「이름 바꾸기」 가 브라우저 prompt(자동 취소됨)라 안 되던 것 → 줄 안에서 고침 - 표시 형식 「@」 를 수에 걸면 빈 칸이 되던 것 → 엑셀처럼 일반으로 보임 - 연속 데이터 채우기 · 텍스트 나누기가 칸 서식을 지우던 것 → 서식 유지 - 틀 고정 「지금 칸 기준」 을 먼 칸(Z1000)에서 하면 고정 판이 격자를 다 덮어 못 움직이던 것 → 화면 안쪽 칸에서만(안내) ORCA(IP http://100.72.32.27:5181 · 메인 폴더 탭 · 판정은 수치): 목록 80 항목 밝은 80/80 · 어두운 80/80 · 사각형 겹침 · 잘림 · 화면 밖 · 대비 3 미만 0 건. 시험 — test_stage3_autosum (여러 칸 넷 더함) · test_stage2_data_tools · test_stage2_format_code · spreadsheet_b_test_entry 197/197(@ 하나 더함) · test_spreadsheet_size(css gzip 3121 · 합 67403) 통과. 시험으로 바꾼 칸은 저장 안 하고 「다시 열기」 로 버림. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W9YEkMnYQnNnRZRPXoxFsw
103 lines
4.5 KiB
TypeScript
103 lines
4.5 KiB
TypeScript
/* =============================================================================
|
|
* spreadsheet_autosum.ts (3단계 · 주인 sub_laptop_4)
|
|
* 자동 합계(Alt+= · 도구 단추 Σ) — 엑셀 규칙: 활성 칸 바로 위가 숫자면 그 위 연속 숫자 칸을
|
|
* 위로 죽 잡고, 아니면 바로 왼쪽이 숫자면 왼쪽으로 죽 잡음. 여러 칸을 고른 채면 그 고름
|
|
* 안에서(활성 칸 위 또는 왼쪽만) 잡음. 아무것도 못 찾으면 빈 `=SUM()`.
|
|
*
|
|
* 잇는 법(D · E · sub7 — 지금은 하나도 안 이어짐):
|
|
* 1) `spreadsheet_keys.ts` 에 Alt+= 를 매핑해 `applyAutoSum(ctx)` 를 부름.
|
|
* 2) `spreadsheet_toolbar.ts` 에 Σ 단추를 두고 눌리면 `applyAutoSum(ctx)` 를 부름.
|
|
* 3) 지금은 칸에 식 글만 바로 넣음(편집기를 안 거침) — 사용자가 범위를 눈으로 고쳐 잡게 하려면
|
|
* `ctx.editor.begin("=SUM(...)")` 로 편집 상태로 열고 커밋은 사용자가 Enter.
|
|
* ========================================================================== */
|
|
|
|
import { rangeToA1, toA1 } from "./spreadsheet_address";
|
|
import { isFrac } from "./spreadsheet_functions_math";
|
|
import type { Cell, CellRange } from "./spreadsheet_types";
|
|
import type { SpreadsheetContext } from "./spreadsheet_view_types";
|
|
|
|
function isNumericCell(ctx: SpreadsheetContext, sheet: string, r: number, c: number): boolean {
|
|
if (r < 0 || c < 0) return false;
|
|
return isFrac(ctx.engine.value(sheet, r, c));
|
|
}
|
|
|
|
function verticalRun(
|
|
ctx: SpreadsheetContext,
|
|
sheet: string,
|
|
r: number,
|
|
c: number,
|
|
top: number,
|
|
): CellRange | null {
|
|
if (r - 1 < top || !isNumericCell(ctx, sheet, r - 1, c)) return null;
|
|
let start = r - 1;
|
|
while (start - 1 >= top && isNumericCell(ctx, sheet, start - 1, c)) start--;
|
|
return { r0: start, c0: c, r1: r - 1, c1: c };
|
|
}
|
|
|
|
function horizontalRun(
|
|
ctx: SpreadsheetContext,
|
|
sheet: string,
|
|
r: number,
|
|
c: number,
|
|
left: number,
|
|
): CellRange | null {
|
|
if (c - 1 < left || !isNumericCell(ctx, sheet, r, c - 1)) return null;
|
|
let start = c - 1;
|
|
while (start - 1 >= left && isNumericCell(ctx, sheet, r, start - 1)) start--;
|
|
return { r0: r, c0: start, r1: r, c1: c - 1 };
|
|
}
|
|
|
|
/** 활성 칸 기준 합산 범위를 찾음 — 없으면 null(빈 `=SUM()`). */
|
|
export function autoSumRange(ctx: SpreadsheetContext): CellRange | null {
|
|
const sheet = ctx.selection.시트;
|
|
const { r, c } = ctx.selection.활성;
|
|
const sel = ctx.selection.범위[0];
|
|
|
|
if (sel && (sel.r1 > sel.r0 || sel.c1 > sel.c0)) {
|
|
if (r > sel.r0) return { r0: sel.r0, c0: c, r1: r - 1, c1: c };
|
|
if (c > sel.c0) return { r0: r, c0: sel.c0, r1: r, c1: c - 1 };
|
|
}
|
|
|
|
return verticalRun(ctx, sheet, r, c, 0) ?? horizontalRun(ctx, sheet, r, c, 0);
|
|
}
|
|
|
|
/** 여러 칸을 고른 채 Σ — 엑셀처럼 열마다(한 행만 골랐으면 행 끝) 고름 끝 빈 칸 또는 바로 다음 빈 칸에
|
|
* 합계 식을 바로 넣을 칸들. 고른 칸이 하나거나 넣을 빈 칸이 없으면 null. */
|
|
export function autoSumCells(ctx: SpreadsheetContext): Record<string, Cell> | null {
|
|
const sel = ctx.selection.범위[0];
|
|
if (!sel || (sel.r0 === sel.r1 && sel.c0 === sel.c1)) return null;
|
|
const sheet = ctx.sheet();
|
|
const empty = (r: number, c: number): boolean => {
|
|
const x = sheet.칸[toA1(r, c)];
|
|
return !x || (x.식 === undefined && (x.값 === undefined || x.값 === ""));
|
|
};
|
|
const across = sel.r0 === sel.r1;
|
|
const out: Record<string, Cell> = {};
|
|
const lines = across
|
|
? [sel.r0]
|
|
: Array.from({ length: sel.c1 - sel.c0 + 1 }, (_, i) => sel.c0 + i);
|
|
for (const k of lines) {
|
|
const at = (i: number): [number, number] => (across ? [k, i] : [i, k]);
|
|
const first = across ? sel.c0 : sel.r0;
|
|
const last = across ? sel.c1 : sel.r1;
|
|
const end = empty(...at(last)) ? last : last + 1;
|
|
if (!empty(...at(end))) continue;
|
|
const [r, c] = at(end);
|
|
const [r0, c0] = at(first);
|
|
const [r1, c1] = at(end - 1);
|
|
const key = toA1(r, c);
|
|
const 식 = `SUM(${rangeToA1({ r0, c0, r1, c1 })})`;
|
|
const 서식 = sheet.칸[key]?.서식;
|
|
out[key] = 서식 === undefined ? { 식 } : { 식, 서식 };
|
|
}
|
|
return Object.keys(out).length ? out : null;
|
|
}
|
|
|
|
/** 활성 칸에 `=SUM(...)` 을 바로 넣음(칸 명령 — 편집기를 안 거침). */
|
|
export function applyAutoSum(ctx: SpreadsheetContext): void {
|
|
const range = autoSumRange(ctx);
|
|
const { r, c } = ctx.selection.활성;
|
|
const 식 = range ? `SUM(${rangeToA1(range)})` : "SUM()";
|
|
ctx.dispatch({ 종류: "칸", 시트: ctx.selection.시트, 칸: { [toA1(r, c)]: { 식 } } });
|
|
}
|