feat(spreadsheet): 산출근거 스프레드시트 0 계약 — 타입 · 엔진 머리 · 저장 견본
- spreadsheet_types.ts · spreadsheet_view_types.ts — 문서 · 칸 · 서식 · 수식 나무 · 값 · 명령 · 계산 엔진 · 화면 문맥 계약 - 머리 주석에 파일 · 주인(A~F) · 예산 줄 수 · 크기 예산(gzip 54 KB) · 규칙(외부 라이브러리 0 · M02 만 늦게 불러옴) - A · B 엔진 · C 격자 · D 진입 · E 도구 공개 함수 머리(빈 몸) - spreadsheet_sample.json — basis/<열 id>.json 한 벌 견본(병합 · 서식 · 다른 시트 참조 · 숨김 · 계산값) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0168FQuoV7vDh5nhnSowW5cp
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet.ts (주인 D)
|
||||
* 산출근거 스프레드시트 진입 — `createSpreadsheet(칸, 통합문서, {readOnly, onChange, onSelect})` →
|
||||
* `{getDoc, setDoc, recalc, destroy}`(표 부품 `createSheet` 와 같은 모양 · M02 는 부르는 줄만 바꿈).
|
||||
* 문맥(`SpreadsheetContext`)을 만들어 C 격자 · E 도구 · 탭 · 메뉴 · 클립보드를 붙이고 D 입력(고름 · 편집기 ·
|
||||
* 키 · 마우스)을 잇음. 한글 조합 = 숨은 textarea 에 늘 초점 · compositionstart 로 편집 시작(keydown 글자 방식 금지).
|
||||
* ⚠ M02 만 `import()` 로 불러옴 — 정적 import 금지(다른 페이지 번들에 안 섞이게).
|
||||
* 저장은 부른 쪽 몫(`onChange` 로 문서 · 자동저장 없음 · [저장] 때 서버가 Node 로 다시 풂).
|
||||
* 0 계약 머리 — 몸은 D 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CalcValues, Workbook } from "./spreadsheet_types";
|
||||
|
||||
export type { Workbook, CalcValues } from "./spreadsheet_types";
|
||||
|
||||
export interface SpreadsheetOptions {
|
||||
/** 읽기만 — 도구 모음 · 메뉴 없음 · 편집 · 붙여넣기 · 끌기 막음(고르기 · 복사는 됨) */
|
||||
readOnly?: boolean;
|
||||
/** 명령마다 문서 사본 — 페이지가 캐시(sessionStorage)에 쌓고 [저장] 단추를 켬 */
|
||||
onChange?: (book: Workbook) => void;
|
||||
/** 고름이 바뀜 — 시트 id · 활성 칸 A1 · 범위 A1 */
|
||||
onSelect?: (selection: { 시트: string; 칸: string; 범위: string }) => void;
|
||||
}
|
||||
|
||||
export interface SpreadsheetHandle {
|
||||
/** 문서 사본 */
|
||||
getDoc(): Workbook;
|
||||
/** 문서 바꿔 끼움 — 되돌리기 이력 비움 · 고름 A1 */
|
||||
setDoc(book: Workbook): void;
|
||||
/** 전부 다시 풀고 다시 그림 · 결과(저장 모양) */
|
||||
recalc(): CalcValues;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet: 아직 없음(D)");
|
||||
};
|
||||
|
||||
export function createSpreadsheet(
|
||||
_host: HTMLElement,
|
||||
_book: Workbook,
|
||||
_opts: SpreadsheetOptions,
|
||||
): SpreadsheetHandle {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_address.ts (주인 A)
|
||||
* 주소 — A1 ↔ 행열(0 부터) · 범위 글 · 열 글자 · 시트 이름 따옴표 · Map 열쇠.
|
||||
* 0 계약 머리 — 몸은 A 가 채움. 계약은 `spreadsheet_types.ts`.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CellAddress, CellRange } from "./spreadsheet_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_address: 아직 없음(A)");
|
||||
};
|
||||
|
||||
/** 0 → `A` · 27 → `AB` */
|
||||
export function colName(_c: number): string {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** `AB` → 27 · 못 읽으면 -1 (대소문자 가리지 않음) */
|
||||
export function colIndex(_letters: string): number {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** (30, 4) → `E31` */
|
||||
export function toA1(_r: number, _c: number): string {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** `E31` · `$E$31` → {r:30, c:4} · 못 읽으면 null */
|
||||
export function parseA1(_text: string): CellAddress | null {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 칸 하나면 `E31` · 아니면 `C3:AD3` · 행 전체 `3:3` · 열 전체 `A:A` */
|
||||
export function rangeToA1(_range: CellRange): string {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** `C3:AD3` · `E31` · `A:A` · `3:5` → 범위(뒤집힌 글은 바로 세움) · 못 읽으면 null */
|
||||
export function parseRange(_text: string): CellRange | null {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 식에 넣을 시트 이름 — 글자 · 숫자 · _ 밖이 있으면 `'…'`(안의 `'` 는 `''`) */
|
||||
export function quoteSheet(_name: string): string {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** Map 열쇠 — r × MAX_COLS + c */
|
||||
export function cellId(_r: number, _c: number): number {
|
||||
return todo();
|
||||
}
|
||||
|
||||
export function fromCellId(_id: number): CellAddress {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_clipboard.ts (주인 E)
|
||||
* 복사 · 잘라내기 · 붙여넣기 — `copy` · `cut` · `paste` 사건만(허락 창 없음 · navigator.clipboard 안 씀).
|
||||
* 형 · 수식 살리기 규칙은 `spreadsheet_types.ts` `ClipBlock` 머리. 잘라 붙이기(안쪽) = `옮기기` 명령 ·
|
||||
* 나머지 = `칸` 명령(식은 A `moveFormula` · 구글 R1C1 은 `r1c1ToA1`) + 병합 명령 묶음.
|
||||
* 편집 중(`ctx.editor.editing()`)이면 사건을 편집기에 맡김. 0 계약 머리 — 몸은 E 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CellAddress, CellRange, ClipBlock, Workbook } from "./spreadsheet_types";
|
||||
import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_clipboard: 아직 없음(E)");
|
||||
};
|
||||
|
||||
/** `ctx.root` 의 copy · cut · paste 를 받음(root null) */
|
||||
export function attachClipboard(_ctx: SpreadsheetContext): PartHandle {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 클립보드 글 → 뭉치(HTML 먼저 · 없으면 TSV) · 읽을 것 없으면 null · `at` = 붙일 왼위(구글 R1C1 풀이) */
|
||||
export function parseClipboard(_html: string, _text: string, _at: CellAddress): ClipBlock | null {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 범위 → 클립보드 글 둘(TSV = 보이는 값 · HTML = 인라인 서식 표 + 안쪽 뭉치 JSON) */
|
||||
export function blockToClipboard(
|
||||
_book: Workbook,
|
||||
_sheet: string,
|
||||
_range: CellRange,
|
||||
): { html: string; text: string } {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_commands.ts (주인 A)
|
||||
* 명령 적용 — 문서를 그 자리에서 바꾸고 되돌림 짝 · 바뀐 칸 · 통째 다시 여부를 돌려줌.
|
||||
* 병합 규칙(엑셀): 병합 일부를 가르는 칸 쓰기 · 옮기기는 막음(오류 던짐 → 화면이 알림) ·
|
||||
* 병합 안에 행열을 넣으면 병합이 늘고 지우면 줆 · 병합하면 왼위 칸 값만 남김.
|
||||
* 서식 표는 같은 서식을 한 번만 둠(칸은 번호) · 안 쓰는 서식 정리는 저장 전에(`compactStyles`).
|
||||
* 0 계약 머리 — 몸은 A 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { Command, CommandEffect, Sheet, Workbook } from "./spreadsheet_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_commands: 아직 없음(A)");
|
||||
};
|
||||
|
||||
/** 막힌 명령(병합 가름 · 마지막 시트 지우기 · 같은 시트 이름 …) — 화면이 `message` 를 알림 */
|
||||
export class CommandError extends Error {}
|
||||
|
||||
export function applyCommand(_book: Workbook, _command: Command): CommandEffect {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 빈 통합문서 — 시트 하나(`s1` · 이름은 부른 쪽) · 서식 [{}] */
|
||||
export function emptyWorkbook(_column: string, _sheetName: string): Workbook {
|
||||
return todo();
|
||||
}
|
||||
|
||||
export function emptySheet(_id: string, _name: string): Sheet {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 안 쓰는 서식을 빼고 번호를 다시 매김 — 저장 직전(되돌리기 짝은 안 만듦) */
|
||||
export function compactStyles(_book: Workbook): void {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_eval.ts (주인 A)
|
||||
* 나무 풀기 · 오류 값 전파 · 형 바꾸기(엑셀 규칙). B 의 함수들이 형 바꾸기 도우미를 씀.
|
||||
* 빈 칸 = 수에서 0 · 글에서 "" · `"12"+1 = 13` · `TRUE+1 = 2` · 글이 수로 안 읽히면 `#VALUE!`.
|
||||
* 무리수(SQRT · 삼각 · PI · 소수 거듭제곱)는 double → 유효 15 자리 십진 → 분수(B · A 같이 `fromDouble`).
|
||||
* 0 계약 머리 — 몸은 A 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type {
|
||||
CalcValue,
|
||||
ErrorCode,
|
||||
ErrorValue,
|
||||
EvalContext,
|
||||
EvalResult,
|
||||
FormulaNode,
|
||||
Frac,
|
||||
Scalar,
|
||||
} from "./spreadsheet_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_eval: 아직 없음(A)");
|
||||
};
|
||||
|
||||
export function evaluate(_node: FormulaNode, _ctx: EvalContext): EvalResult {
|
||||
return todo();
|
||||
}
|
||||
|
||||
export function err(_code: ErrorCode, _why?: string): ErrorValue {
|
||||
return todo();
|
||||
}
|
||||
|
||||
export function isError(_value: unknown): _value is ErrorValue {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 수로 — 빈 칸 0 · 참거짓 1/0 · 수 글 · 아니면 `#VALUE!` · 범위는 칸 하나일 때만 */
|
||||
export function asNumber(_value: EvalResult): Frac | ErrorValue {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 글로 — 수는 일반 형식 글(엑셀 `&` 와 같게) · 참거짓 `TRUE`/`FALSE` */
|
||||
export function asText(_value: EvalResult): string | ErrorValue {
|
||||
return todo();
|
||||
}
|
||||
|
||||
export function asBool(_value: EvalResult): boolean | ErrorValue {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** double → 유효 15 자리 십진 → 분수 · NaN · 무한은 `#NUM!` */
|
||||
export function fromDouble(_x: number): Frac | ErrorValue {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 저장 모양 — 수는 십진 글(30 자리까지) */
|
||||
export function toCalcValue(_value: Scalar): CalcValue {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_fill.ts (주인 B)
|
||||
* 채우기 핸들 — 원본 범위를 대상 범위로 늘림: 수 하나 = 복사 · 수 둘 이상 = 등차 연속 ·
|
||||
* 글 끝 번호(`구-01` → `구-02`) · 식 = 상대 참조 옮김(A `moveFormula`) · 서식 같이.
|
||||
* 결과는 `{ 종류: "칸" }` 명령에 넣을 칸 뭉치. 0 계약 머리 — 몸은 B 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { Cell, CellRange, Workbook } from "./spreadsheet_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_fill: 아직 없음(B)");
|
||||
};
|
||||
|
||||
/** `target` 은 `source` 를 한 방향으로 늘린 범위(방향은 둘을 견줘 앎) · A1 → 칸(null = 비움) */
|
||||
export function fillCells(
|
||||
_book: Workbook,
|
||||
_sheet: string,
|
||||
_source: CellRange,
|
||||
_target: CellRange,
|
||||
): Record<string, Cell | null> {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_functions.ts (주인 B)
|
||||
* 함수 표 — 이름(대문자) → 뜻. 몸은 `spreadsheet_functions_math.ts` · `_misc.ts` 에 두고 여기서 모음.
|
||||
* 1단계 약 45 — 수학 SUM PRODUCT SUMPRODUCT ROUND ROUNDUP ROUNDDOWN INT TRUNC CEILING FLOOR ABS MOD
|
||||
* POWER SQRT PI SIN COS TAN ASIN ACOS ATAN RADIANS DEGREES MIN MAX AVERAGE · 논리 IF AND OR NOT IFERROR ·
|
||||
* 글 CONCATENATE FIXED TEXT LEN LEFT RIGHT MID VALUE · 찾기 VLOOKUP HLOOKUP INDEX MATCH CHOOSE ·
|
||||
* 세기 COUNT COUNTA COUNTIF SUMIF. 뜻 · 경계값은 엑셀과 같게(Formula.js 는 시험 대조 답으로만).
|
||||
* 없는 이름 = `#NAME?` · 인자 수가 min · max 밖이면 A 의 eval 이 `#VALUE!` + 까닭(엑셀은 입력을 막음).
|
||||
* 0 계약 머리 — 몸은 B 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { SheetFunction } from "./spreadsheet_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_functions: 아직 없음(B)");
|
||||
};
|
||||
|
||||
export function getFunction(_name: string): SheetFunction | undefined {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 아는 함수 이름 전부(가나다 · 자동 완성은 2단계) */
|
||||
export function functionNames(): string[] {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_graph.ts (주인 A)
|
||||
* 계산 엔진 — 식마다 가리키는 칸 · 범위로 의존 그래프 · 바뀐 칸에 딸린 칸만 차례로 다시 풂.
|
||||
* 순환은 고리의 칸마다 `#CYCLE!`(까닭 = 고리 칸 목록) · 반복 계산 안 함 · 나머지 칸은 계속 풂.
|
||||
* 식 나무는 식 글마다 캐시. 0 계약 머리 — 몸은 A 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CalcEngine, Workbook } from "./spreadsheet_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_graph: 아직 없음(A)");
|
||||
};
|
||||
|
||||
/** 문서를 참조로 쥠 — 만들 때 전부 풂. */
|
||||
export function createCalcEngine(_book: Workbook): CalcEngine {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_grid.ts (주인 C)
|
||||
* 격자 — DOM(칸 = 절대 위치 div) · 틀고정 4 판(왼위 · 위 · 왼 · 본문) · 판마다 보이는 행열만 그림 ·
|
||||
* 병합 칸 = div 하나 · 숨긴 행열 건너뜀 · 눈금선 · 고름 · 참조 색 테두리 · 채우기 손잡이.
|
||||
* 칸 글은 엔진 값 → B `formatValue` → 서식 CSS(`spreadsheet_cellstyle.ts`). 머리 · 폭 끌기는 `spreadsheet_headers.ts`.
|
||||
* 폭 · 높이 끌기는 `ctx.dispatch({종류:"열폭"|"행높이"})`. 0 계약 머리 — 몸은 C 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { GridHandle, SpreadsheetContext } from "./spreadsheet_view_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_grid: 아직 없음(C)");
|
||||
};
|
||||
|
||||
/** `ctx.grid` 는 D 가 이 결과로 채움 — 만들 때 `ctx.grid` · `ctx.editor` 를 부르지 말 것 */
|
||||
export function createGrid(_ctx: SpreadsheetContext): GridHandle {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_history.ts (주인 A)
|
||||
* 되돌리기 · 다시 — 명령과 되돌림 짝을 쌓음(메모리만 · 캐시에 안 넣음).
|
||||
* 0 계약 머리 — 몸은 A 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { History } from "./spreadsheet_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_history: 아직 없음(A)");
|
||||
};
|
||||
|
||||
/** `limit` = 쌓을 수(넘으면 오래된 것부터 버림) */
|
||||
export function createHistory(_limit?: number): History {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_menu.ts (주인 E)
|
||||
* 우클릭 메뉴 — 칸 · 행 머리 · 열 머리마다: 잘라내기 · 복사 · (붙여넣기 = 「Ctrl+V 를 쓸 것」 안내) ·
|
||||
* 행열 넣기 · 지우기 · 숨기기 · 보이기 · 내용 지우기 · 서식 지우기 · 병합 · 풀기.
|
||||
* 0 계약 머리 — 몸은 E 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_menu: 아직 없음(E)");
|
||||
};
|
||||
|
||||
/** `ctx.root` 의 contextmenu 를 받음(root null) */
|
||||
export function attachMenu(_ctx: SpreadsheetContext): PartHandle {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_numfmt.ts (주인 B)
|
||||
* 숫자 형식 코드 → 보이는 글 — 엑셀 코드 부분 집합: 일반 · `0` `#` `?` `.` `,`(천 단위 · 끝 쉼표 = 천으로 나눔) ·
|
||||
* 구역 `;`(양 · 음 · 0 · 글) · `"글"` · `\x` · `_x`(폭만큼 띄움) · `*x`(채움) · `[Red]` 등 색 · `%` · `@` · `E+00`.
|
||||
* 실무 형식(`0.00` · `_-* #,##0.00_-;\-* #,##0.00_-;_-* "-"_-;_-@_-` · `0.00_);[Red]\(0.00\)` …)은 모두 맞아야 함.
|
||||
* 끝수는 분수로 사사오입(엑셀 표시와 같음). 날짜 · 시간 형식은 1단계 밖(글 그대로).
|
||||
* 0 계약 머리 — 몸은 B 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { FormattedText, Scalar } from "./spreadsheet_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_numfmt: 아직 없음(B)");
|
||||
};
|
||||
|
||||
/** `code` 없음 = 일반(수는 유효 11 자리 안쪽 · 칸 폭은 격자 몫) · 오류 값은 코드 글 그대로 */
|
||||
export function formatValue(_value: Scalar, _code: string | undefined): FormattedText {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_parser.ts (주인 A)
|
||||
* 수식 글 → 낱말 · 나무 · 나무 → 글. `eval` 금지 — 직접 짠 파서만.
|
||||
* 우선순위는 엑셀과 같음: 범위 `:` > 부호 `-` `+` > `%` > `^` > `* /` > `+ -` > `&` > 비교
|
||||
* (부호가 `^` 보다 먼저 — `-2^2 = 4`). 같은 단은 왼쪽부터(`^` 도 왼쪽부터 · 엑셀과 같음).
|
||||
* 글 `"…"` 안 `""` = 따옴표 하나 · 시트 `'이름'!A1` · `이름!A1` · 오류 값 글자 · TRUE/FALSE.
|
||||
* 0 계약 머리 — 몸은 A 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { FormulaNode, ParseResult, Token } from "./spreadsheet_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_parser: 아직 없음(A)");
|
||||
};
|
||||
|
||||
/** 앞 `=` 뺀 식 글 → 낱말(빈칸 낱말 포함 · 끝까지 · 못 읽는 글은 `unknown`) — 편집기 색칠 · 가리키기용 */
|
||||
export function tokenize(_formula: string): Token[] {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 앞 `=` 뺀 식 글 → 나무. 앞에 `=` 가 붙어 와도 됨. 함수 이름은 대문자로. */
|
||||
export function parseFormula(_formula: string): ParseResult {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 나무 → 앞 `=` 뺀 식 글(참조 옮김 뒤 다시 적기) — 수는 원래 글(`text`) 그대로 */
|
||||
export function formulaToText(_node: FormulaNode): string {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_recalc.ts (주인 A)
|
||||
* 통합문서 한 벌 풀이 — 서버 Node 진입(`common_util_spreadsheet_node.ts`) · 시험이 부름.
|
||||
* 화면과 같은 엔진(`createCalcEngine`)을 한 번 돌려 `snapshot()` — 계산이 두 벌이 되지 않게.
|
||||
* 0 계약 머리 — 몸은 A 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CalcValues, Workbook } from "./spreadsheet_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_recalc: 아직 없음(A)");
|
||||
};
|
||||
|
||||
export function recalcWorkbook(_book: Workbook): CalcValues {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_refshift.ts (주인 A)
|
||||
* 참조 옮김 — 행열 넣기 · 지우기 · 잘라 붙이기 · 시트 이름 바꾸기 때 식 글을 고쳐 적음(엑셀 규칙) ·
|
||||
* 복사 · 채우기 때 상대 참조만 옮김 · 구글 R1C1 식 → A1.
|
||||
* 지운 칸 · 격자 밖으로 나간 참조는 `#REF!` · 범위는 남은 만큼 줄어듦(끝까지 지우면 `#REF!`).
|
||||
* 0 계약 머리 — 몸은 A 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { CellAddress, Command, Workbook } from "./spreadsheet_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_refshift: 아직 없음(A)");
|
||||
};
|
||||
|
||||
/** 행열 · 옮기기 · 시트이름 명령에 맞춰 식 글 고침(식이 든 시트 id = `host`) — 안 바뀌면 같은 글 */
|
||||
export function shiftForCommand(
|
||||
_formula: string,
|
||||
_host: string,
|
||||
_command: Command,
|
||||
_book: Workbook,
|
||||
): string {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 복사 · 채우기 — 상대 참조만 (dr, dc) 옮김 · 격자 밖은 `#REF!` */
|
||||
export function moveFormula(_formula: string, _dr: number, _dc: number): string {
|
||||
return todo();
|
||||
}
|
||||
|
||||
/** 구글 `data-sheets-formula`(`=2*R[0]C[-1]`) → 붙일 자리 기준 A1 식(앞 `=` 뺌) */
|
||||
export function r1c1ToA1(_formula: string, _at: CellAddress): string {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"종류": "통합문서",
|
||||
"판": 1,
|
||||
"열": "h_soil_ditch",
|
||||
"서식": [
|
||||
{},
|
||||
{ "크기": 12, "굵게": true },
|
||||
{
|
||||
"가로": "center",
|
||||
"세로": "center",
|
||||
"채움": "#F2F2F2",
|
||||
"테두리": {
|
||||
"위": { "선": "thin" },
|
||||
"아래": { "선": "thin" },
|
||||
"왼": { "선": "thin" },
|
||||
"오른": { "선": "thin" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"가로": "distributed",
|
||||
"세로": "center",
|
||||
"테두리": { "위": { "선": "hair" }, "아래": { "선": "hair" } }
|
||||
},
|
||||
{ "세로": "center", "테두리": { "위": { "선": "hair" }, "아래": { "선": "hair" } } },
|
||||
{
|
||||
"세로": "center",
|
||||
"형식": "0.00",
|
||||
"테두리": { "위": { "선": "hair" }, "아래": { "선": "hair" } }
|
||||
},
|
||||
{ "형식": "0.00" }
|
||||
],
|
||||
"시트": [
|
||||
{
|
||||
"id": "s1",
|
||||
"이름": "토사개거",
|
||||
"칸": {
|
||||
"A1": { "값": "□수로형 토사개거 산출근거", "서식": 1 },
|
||||
"A3": { "값": "공종", "서식": 2 },
|
||||
"B3": { "값": "규격", "서식": 2 },
|
||||
"C3": { "값": "산출 내역", "서식": 2 },
|
||||
"D3": { "서식": 2 },
|
||||
"E3": { "서식": 2 },
|
||||
"F3": { "서식": 2 },
|
||||
"G3": { "값": "단위", "서식": 2 },
|
||||
"H3": { "값": "수량", "서식": 2 },
|
||||
"A4": { "값": "터파기", "서식": 3 },
|
||||
"B4": { "값": "토사", "서식": 4 },
|
||||
"C4": { "값": "(윗폭+밑폭)÷2×깊이", "서식": 4 },
|
||||
"G4": { "값": "㎥", "서식": 4 },
|
||||
"H4": { "식": "ROUNDDOWN((J4+J5)/2*J6,2)", "서식": 5 },
|
||||
"A5": { "값": "되메우기", "서식": 3 },
|
||||
"B5": { "값": "토사", "서식": 4 },
|
||||
"C5": { "값": "터파기×0.3", "서식": 4 },
|
||||
"G5": { "값": "㎥", "서식": 4 },
|
||||
"H5": { "식": "ROUNDDOWN(H4*0.3,2)", "서식": 5 },
|
||||
"A6": { "값": "계", "서식": 3 },
|
||||
"H6": { "식": "SUM(H4:H5)", "서식": 5 },
|
||||
"J4": { "식": "치수!B1", "서식": 6 },
|
||||
"J5": { "값": 0.3, "서식": 6 },
|
||||
"J6": { "값": 0.3, "서식": 6 }
|
||||
},
|
||||
"병합": ["A1:H1", "C3:F3", "C4:F4", "C5:F5", "A6:G6"],
|
||||
"열": {
|
||||
"A": { "폭": 10 },
|
||||
"C": { "폭": 6 },
|
||||
"D": { "폭": 6 },
|
||||
"E": { "폭": 6 },
|
||||
"F": { "폭": 6 },
|
||||
"J": { "숨김": true }
|
||||
},
|
||||
"행": { "1": { "높이": 20 } },
|
||||
"틀고정": { "행": 3 },
|
||||
"보기": { "눈금선": false }
|
||||
},
|
||||
{
|
||||
"id": "s2",
|
||||
"이름": "치수",
|
||||
"칸": {
|
||||
"A1": { "값": "윗폭(m)" },
|
||||
"B1": { "값": 0.9, "서식": 6 }
|
||||
}
|
||||
}
|
||||
],
|
||||
"활성": "s1",
|
||||
"계산값": {
|
||||
"s1": {
|
||||
"H4": { "수": "0.18" },
|
||||
"H5": { "수": "0.05" },
|
||||
"H6": { "수": "0.23" },
|
||||
"J4": { "수": "0.9" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_tabs.ts (주인 E)
|
||||
* 시트 탭 — 고르기(`ctx.showSheet`) · 더하기 · 이름 바꾸기(두 번 누르기) · 지우기 · 끌어 옮기기 · 복사.
|
||||
* 이름 바꾸기 · 지우기 · 옮기기는 명령(`시트이름` · `시트지우기` · `시트옮기기` · `시트더하기`).
|
||||
* 0 계약 머리 — 몸은 E 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_tabs: 아직 없음(E)");
|
||||
};
|
||||
|
||||
export function mountTabs(_ctx: SpreadsheetContext): PartHandle {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_toolbar.ts (주인 E)
|
||||
* 서식 도구 모음 — 되돌리기 · 다시 · 글꼴 · 크기 · 굵게 · 기울임 · 밑줄 · 글자색 · 채움색 · 가로 · 세로 정렬 ·
|
||||
* 줄 바꿈 · 테두리(위치 × 선) · 숫자 형식 · 병합 · 틀고정 · 눈금선. 모두 `ctx.dispatch` 명령으로.
|
||||
* 활성 칸 서식을 `refresh()` 때 단추 상태로. readOnly 면 안 붙임(root null).
|
||||
* 0 계약 머리 — 몸은 E 가 채움.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types";
|
||||
|
||||
const todo = (): never => {
|
||||
throw new Error("spreadsheet_toolbar: 아직 없음(E)");
|
||||
};
|
||||
|
||||
export function mountToolbar(_ctx: SpreadsheetContext): PartHandle {
|
||||
return todo();
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_types.ts
|
||||
* 산출근거 스프레드시트 계약 — 저장 문서 · 칸 · 서식 · 수식 나무 · 값 · 명령 · 계산 엔진 모양.
|
||||
* 근거 `tmp/M02_분석/9_엑셀검토.md` · PLAN 11-8. 이 파일 · `spreadsheet_view_types.ts` · 견본 JSON 은
|
||||
* 0 계약 주인만 고침 — 다른 창은 브레인에게 고침을 청함. 타입만 둠(서버 Node · 시험이 그대로 씀).
|
||||
*
|
||||
* ── 규칙 ────────────────────────────────────────────────────────────────────
|
||||
* · 외부 라이브러리 0 · 오픈소스 코드 안 베낌(Formula.js · ssf 는 시험 대조 답으로만).
|
||||
* · 가져다 쓰는 것 = `@ui/ui_template_elements`(el · createButton · 이미 앱 번들) ·
|
||||
* `@ui/sheet/ui_template_sheet_frac`(BigInt 분수 · 집계표와 끝수 한 벌) 둘뿐.
|
||||
* · M02 만 `import()` 로 늦게 불러옴 — 다른 페이지 · `A00_Common/main.ts` 가 정적 import 금지.
|
||||
* · 엔진 파일(A · B)은 DOM 을 모름 — 화면 즉시 풀이와 [저장] 때 서버 Node 풀이가 같은 TS(CLAUDE.md 5장 ②).
|
||||
* · 보이는 칸만 그림 · 파일마다 700 줄 안쪽(예산 넘기 전에 쪼갬) · 화면 글자는 그 파일 안에 둠.
|
||||
* · xlsx 가져오기 · 내보내기 · 인쇄 · PDF 는 만들지 않음(단추도 없음). xlsx 는 시험(F)만 읽음.
|
||||
*
|
||||
* ── 크기 예산 (vite build · 압축(gzip) 뒤 · M02 산출근거를 열 때만 받는 조각) ──────
|
||||
* 엔진 A+B 22 KB · 화면 C+D+E 28 KB · css 4 KB · 합 54 KB 안쪽. 재는 곳 = F(빌드 조각 크기 시험).
|
||||
*
|
||||
* ── 파일 · 주인 · 예산 줄 수 ─────────────────────────────────────────────────
|
||||
* 0 spreadsheet_types.ts 이 계약(타입) ~450
|
||||
* 0 spreadsheet_view_types.ts 화면 부품 사이 계약(DOM 타입) ~150
|
||||
* 0 spreadsheet_sample.json 저장 견본 = `basis/<열 id>.json` 한 벌
|
||||
* A spreadsheet_address.ts A1 ↔ 행열 · 범위 · 열 글자 · 시트 이름 따옴표 ~150
|
||||
* A spreadsheet_parser.ts 낱말 나누기 · 수식 나무 · 나무 → 글 ~500
|
||||
* A spreadsheet_eval.ts 나무 풀기 · 오류 전파 · 형 바꾸기 ~350
|
||||
* A spreadsheet_graph.ts 계산 엔진 — 의존 그래프 · 딸린 칸만 · 순환 ~350
|
||||
* A spreadsheet_refshift.ts 넣기 · 지우기 · 옮기기 · 복사 때 참조 옮김 ~300
|
||||
* A spreadsheet_commands.ts 명령 적용 → 되돌림 짝 · 빈 통합문서 ~550
|
||||
* A spreadsheet_history.ts 되돌리기 · 다시 ~100
|
||||
* A spreadsheet_recalc.ts 통합문서 한 벌 풀이(서버 Node · 시험 진입) ~80
|
||||
* B spreadsheet_functions.ts 함수 표(이름 → 뜻) · 인자 수 검사 ~120
|
||||
* B spreadsheet_functions_math.ts 수학 · 삼각 · 끝수 ~400
|
||||
* B spreadsheet_functions_misc.ts 논리 · 글 · 찾기 · 세기 ~500
|
||||
* B spreadsheet_numfmt.ts 숫자 형식 코드 → 보이는 글 ~400
|
||||
* B spreadsheet_fill.ts 채우기 핸들 연속값 ~200
|
||||
* C spreadsheet_grid.ts 격자 — 보이는 칸만 · 틀고정 4 판 · 병합 · 고름 그림 ~550
|
||||
* C spreadsheet_headers.ts 열 글자 · 행 번호 · 폭 · 높이 끌기 · 숨김 표시 ~350
|
||||
* C spreadsheet_cellstyle.ts 서식 → CSS(hair · 균등 분할 · 겹친 테두리) ~250
|
||||
* C spreadsheet_grid.css ~200
|
||||
* D spreadsheet.ts 진입 createSpreadsheet · 부품 잇기 · 문맥 ~350
|
||||
* D spreadsheet_selection.ts 고름 모델 · Shift/Ctrl 방향 · 병합으로 늘림 ~300
|
||||
* D spreadsheet_editor.ts 칸 편집기 · 수식 입력줄 · 주소 상자 · 한글 조합 · 참조 가리키기 ~600
|
||||
* D spreadsheet_keys.ts 엑셀 단축키 지도 ~300
|
||||
* D spreadsheet_mouse.ts 끌기 고르기 · 채우기 끌기 · 두 번 누르기 ~300
|
||||
* D spreadsheet.css ~150
|
||||
* E spreadsheet_toolbar.ts 서식 도구 모음 ~550
|
||||
* E spreadsheet_tabs.ts 시트 탭 ~300
|
||||
* E spreadsheet_menu.ts 우클릭 메뉴 ~300
|
||||
* E spreadsheet_clipboard.ts 복사 · 잘라내기 · 붙여넣기(TSV · HTML) ~550
|
||||
* E spreadsheet_toolbar.css ~150
|
||||
* F common_util/common_util_spreadsheet_node.ts Node 진입(재계산 · 폴더 밖) ~40
|
||||
* F common_util/common_util_spreadsheet.py 파이썬 껍데기(common_util_node_bundle) ~60
|
||||
* F package.json `build:spreadsheet` · M02 `basis` 종류(Store · Layers) · 옛 산출근거 옮김 ·
|
||||
* M02_MasterTemplete_Structure.ts 끼우기 · resources/tester/test_spreadsheet_*.py(실무 2,699 칸 대조 · 크기)
|
||||
*
|
||||
* ── 좌표 ────────────────────────────────────────────────────────────────────
|
||||
* 안쪽은 0 부터(r = 행 · c = 열) · 저장 · 식 글은 엑셀 A1(1 부터). 행 최대 1,048,576 · 열 최대 16,384(XFD).
|
||||
* ========================================================================== */
|
||||
|
||||
import type { Frac } from "@ui/sheet/ui_template_sheet_frac";
|
||||
|
||||
export type { Frac };
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// 1. 저장 문서 — 구조물집계표 열마다 한 파일 `resources/master_template/basis/<열 id>.json`
|
||||
// (층 규칙은 M02 와 같음 · 프로젝트 `templates/basis/` · `_initial/` 사본). 견본 = spreadsheet_sample.json.
|
||||
// 키는 한글 · 열거 값(정렬 · 선 · 오류)은 엑셀 이름 그대로.
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export const MAX_ROWS = 1_048_576;
|
||||
export const MAX_COLS = 16_384;
|
||||
|
||||
/** 칸에 넣은 값 — 수는 JSON 수(엑셀처럼 유효 15 자리) · 글 · 참거짓. 식 칸에는 없음. */
|
||||
export type CellInput = number | string | boolean;
|
||||
|
||||
export interface Cell {
|
||||
/** 입력 값 — `식` 이 있으면 없음. `'` 로 시작해 친 글은 `'` 를 떼고 글로 둠. */
|
||||
값?: CellInput;
|
||||
/** A1 식 — 앞 `=` 뺌(`ROUNDDOWN((G31+J31/2)*P31,2)`). 행열을 넣고 지우면 글을 고쳐 적음. */
|
||||
식?: string;
|
||||
/** `서식` 표 번호 — 없으면 행 · 열 서식 · 그다음 0(기본). */
|
||||
서식?: number;
|
||||
}
|
||||
|
||||
export type HAlign = "general" | "left" | "center" | "right" | "distributed" | "centerContinuous";
|
||||
export type VAlign = "top" | "center" | "bottom";
|
||||
export type BorderLine = "hair" | "thin" | "medium" | "thick" | "double" | "dotted" | "dashed";
|
||||
|
||||
export interface BorderSide {
|
||||
선: BorderLine;
|
||||
/** `#RRGGBB` · 없으면 검정. */
|
||||
색?: string;
|
||||
}
|
||||
|
||||
/** 칸 서식 한 벌 — 없는 칸은 기본값. 같은 서식은 표에 한 번만(칸은 번호). */
|
||||
export interface CellStyle {
|
||||
글꼴?: string;
|
||||
/** pt */
|
||||
크기?: number;
|
||||
굵게?: boolean;
|
||||
기울임?: boolean;
|
||||
밑줄?: boolean;
|
||||
취소선?: boolean;
|
||||
/** `#RRGGBB` */
|
||||
글자색?: string;
|
||||
/** `#RRGGBB` — 없으면 채움 없음. */
|
||||
채움?: string;
|
||||
가로?: HAlign;
|
||||
세로?: VAlign;
|
||||
줄바꿈?: boolean;
|
||||
테두리?: { 위?: BorderSide; 아래?: BorderSide; 왼?: BorderSide; 오른?: BorderSide };
|
||||
/** 엑셀 숫자 형식 코드 그대로(`0.00` · `#,##0` · `_-* #,##0.00_-;…` · `@`) · 없으면 일반. */
|
||||
형식?: string;
|
||||
}
|
||||
|
||||
export interface ColumnInfo {
|
||||
/** 엑셀 글자 폭 단위(기본 글꼴 숫자 폭 기준) — 화면이 px 로 바꿈. */
|
||||
폭?: number;
|
||||
숨김?: boolean;
|
||||
서식?: number;
|
||||
}
|
||||
|
||||
export interface RowInfo {
|
||||
/** pt */
|
||||
높이?: number;
|
||||
숨김?: boolean;
|
||||
서식?: number;
|
||||
}
|
||||
|
||||
export interface Sheet {
|
||||
/** 안 바뀌는 id(`s1` …) — 이름을 바꿔도 그대로. 식 글은 이름으로 가리킴. */
|
||||
id: string;
|
||||
이름: string;
|
||||
/** A1 열쇠 · 빈 칸은 안 적음. */
|
||||
칸: Record<string, Cell>;
|
||||
/** `C3:AD3` — 겹치지 않음 · 왼위 칸만 값 · 서식은 칸마다(테두리 때문). */
|
||||
병합?: string[];
|
||||
/** 열 글자 열쇠(`A` · `AB`). */
|
||||
열?: Record<string, ColumnInfo>;
|
||||
/** 행 번호 열쇠(1 부터 · `"31"`). */
|
||||
행?: Record<string, RowInfo>;
|
||||
/** 위에서 고정할 행 수 · 왼쪽에서 고정할 열 수. */
|
||||
틀고정?: { 행?: number; 열?: number };
|
||||
보기?: { 눈금선?: boolean };
|
||||
}
|
||||
|
||||
/** 새 시트 기본값 — 값은 사용자 협의로 확정(9_엑셀검토 8장 · 임의 확정 금지). 없으면 A 의 임시값. */
|
||||
export interface WorkbookDefaults {
|
||||
글꼴?: string;
|
||||
크기?: number;
|
||||
열폭?: number;
|
||||
행높이?: number;
|
||||
}
|
||||
|
||||
export interface Workbook {
|
||||
종류: "통합문서";
|
||||
판: 1;
|
||||
/** 구조물집계표 열 id(파일 이름과 같음). */
|
||||
열: string;
|
||||
기본?: WorkbookDefaults;
|
||||
/** 서식 표 — [0] = 기본(빈 `{}`). */
|
||||
서식: CellStyle[];
|
||||
시트: Sheet[];
|
||||
/** 열 때 보일 시트 id. */
|
||||
활성?: string;
|
||||
/** 서버만 씀 — [저장] 때 Node 풀이 결과(시트 id → A1 → 값). 브라우저 값은 안 적음 · 화면은 안 읽음. */
|
||||
계산값?: CalcValues;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// 2. 주소
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export interface CellAddress {
|
||||
r: number;
|
||||
c: number;
|
||||
}
|
||||
|
||||
/** 닫힌 범위 — r0 ≤ r1 · c0 ≤ c1. 행 전체 = c0 0 · c1 MAX_COLS-1(열 전체도 같은 꼴). */
|
||||
export interface CellRange {
|
||||
r0: number;
|
||||
c0: number;
|
||||
r1: number;
|
||||
c1: number;
|
||||
}
|
||||
|
||||
/** 시트까지 붙은 칸 — 엔진 · 명령이 씀. */
|
||||
export interface SheetCellAddress extends CellAddress {
|
||||
시트: string;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// 3. 수식 — 낱말 · 나무 (A 가 만들고 B · D · E 가 읽음)
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export type ErrorCode =
|
||||
"#NULL!" | "#DIV/0!" | "#VALUE!" | "#REF!" | "#NAME?" | "#NUM!" | "#N/A" | "#CYCLE!"; // 순환 참조 — 엑셀엔 없음 · 칸에 보임(9_엑셀검토 3-3)
|
||||
|
||||
export type TokenKind =
|
||||
| "number"
|
||||
| "string"
|
||||
| "bool"
|
||||
| "error"
|
||||
| "ref" // 칸 · 범위 · 행 · 열 참조(시트 붙은 것 포함) — 편집기가 색 테두리로 씀
|
||||
| "func" // 함수 이름(뒤 `(` 앞까지)
|
||||
| "op"
|
||||
| "paren"
|
||||
| "comma"
|
||||
| "space"
|
||||
| "unknown";
|
||||
|
||||
/** 낱말 — `start` · `end` 는 식 글(앞 `=` 뺀) 안 글자 자리. 편집기 참조 가리키기 · 색칠용. */
|
||||
export interface Token {
|
||||
kind: TokenKind;
|
||||
text: string;
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
/** 나무 안 칸 참조 — 시트 없으면 식이 든 시트. `$` 는 abs. */
|
||||
export interface RefCell {
|
||||
r: number;
|
||||
c: number;
|
||||
absR: boolean;
|
||||
absC: boolean;
|
||||
}
|
||||
|
||||
export type FormulaNode =
|
||||
| { type: "number"; value: Frac; text: string }
|
||||
| { type: "string"; value: string }
|
||||
| { type: "bool"; value: boolean }
|
||||
| { type: "error"; code: ErrorCode }
|
||||
/** 칸 하나 */
|
||||
| { type: "cell"; sheet: string | null; ref: RefCell }
|
||||
/** 범위(`A1:B5` · `A:A` · `3:3`) — 행 · 열 전체는 끝을 MAX 로. 한쪽이라도 `#REF!` 면 error 나무 */
|
||||
| { type: "range"; sheet: string | null; from: RefCell; to: RefCell }
|
||||
/** 글자 그대로 남은 이름(정의 없음) → 풀면 `#NAME?` */
|
||||
| { type: "name"; name: string }
|
||||
| { type: "unary"; op: "-" | "+" | "%"; arg: FormulaNode }
|
||||
| {
|
||||
type: "binary";
|
||||
op: "+" | "-" | "*" | "/" | "^" | "&" | "=" | "<>" | "<" | "<=" | ">" | ">=";
|
||||
left: FormulaNode;
|
||||
right: FormulaNode;
|
||||
}
|
||||
| { type: "call"; name: string; args: FormulaNode[] };
|
||||
|
||||
/** 파싱 실패 — 칸에는 식 글을 그대로 두고 값은 `#NAME?`(엑셀은 입력을 막지만 우리는 까닭과 함께 받음). */
|
||||
export interface ParseFailure {
|
||||
ok: false;
|
||||
message: string;
|
||||
/** 식 글 안 자리 */
|
||||
at: number;
|
||||
}
|
||||
|
||||
export type ParseResult = { ok: true; node: FormulaNode } | ParseFailure;
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// 4. 값 — 풀이 중(분수) · 저장(십진 글)
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export interface ErrorValue {
|
||||
error: ErrorCode;
|
||||
/** 풍선 까닭(한글) */
|
||||
why?: string;
|
||||
}
|
||||
|
||||
/** 칸 하나 값 — 수는 BigInt 분수(무리수 함수만 double → 유효 15 자리 십진 → 분수).
|
||||
* null = 빈 칸 — 수에서 0 · 글에서 "" · 범위 함수는 건너뜀(엑셀 규칙). */
|
||||
export type Scalar = Frac | string | boolean | ErrorValue | null;
|
||||
|
||||
/** 범위 값 — 함수가 칸마다 읽음(빈 칸 null). */
|
||||
export interface RangeValue {
|
||||
kind: "range";
|
||||
rows: number;
|
||||
cols: number;
|
||||
at(r: number, c: number): Scalar;
|
||||
}
|
||||
|
||||
export type EvalResult = Scalar | RangeValue;
|
||||
|
||||
/** 저장 · 서버 결과 한 칸 — 수는 십진 글(끝수 뒤 · 30 자리까지). */
|
||||
export type CalcValue =
|
||||
{ 수: string } | { 글: string } | { 참: boolean } | { 오류: ErrorCode; 까닭?: string };
|
||||
|
||||
/** 시트 id → A1 → 값 (식 칸만). */
|
||||
export type CalcValues = Record<string, Record<string, CalcValue>>;
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// 5. 풀이 문맥 · 함수 (A 의 eval ↔ B 의 함수 표)
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export interface EvalContext {
|
||||
/** 식이 든 칸 */
|
||||
sheet: string;
|
||||
r: number;
|
||||
c: number;
|
||||
/** 칸 값 — 식 칸이면 엔진이 먼저 풀어 줌 · 순환이면 `#CYCLE!` */
|
||||
cell(sheet: string, r: number, c: number): Scalar;
|
||||
range(sheet: string, range: CellRange): RangeValue;
|
||||
/** 시트 이름 → id (없으면 null → `#REF!`) */
|
||||
sheetId(name: string): string | null;
|
||||
}
|
||||
|
||||
/** 인자는 늦게 풂(IF · IFERROR · AND 가 고른 것만 풀게). 범위 인자는 RangeValue 로 옴. */
|
||||
export type LazyArg = () => EvalResult;
|
||||
|
||||
export interface SheetFunction {
|
||||
/** 인자 수 — 최대 없으면 끝없음 */
|
||||
min: number;
|
||||
max?: number;
|
||||
call(args: LazyArg[], ctx: EvalContext): EvalResult;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// 6. 명령 — 문서를 바꾸는 길은 이것뿐(되돌리기 · 서버 재계산 · 캐시가 한 길로 감)
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export type Command =
|
||||
/** 칸 여럿 한 번에 — null = 칸 지움(값 · 식 · 서식 모두). 붙여넣기 · 채우기 · 지우기도 이것. */
|
||||
| { 종류: "칸"; 시트: string; 칸: Record<string, Cell | null> }
|
||||
/** 내용만 지움(Delete) — 서식은 남김 */
|
||||
| { 종류: "내용지움"; 시트: string; 범위: CellRange[] }
|
||||
/** 서식 바꿈 — 칸마다 지금 서식에 `바꿀` 을 덮어 새 서식 번호(표에 없으면 더함). null 항목 = 그 속성 지움 */
|
||||
| {
|
||||
종류: "서식";
|
||||
시트: string;
|
||||
범위: CellRange[];
|
||||
바꿀: { [K in keyof CellStyle]?: CellStyle[K] | null };
|
||||
}
|
||||
| { 종류: "서식지움"; 시트: string; 범위: CellRange[] }
|
||||
/** 행 넣기 · 지우기 — 참조 옮김(지운 칸 참조는 #REF!) · 병합 늘고 줆 */
|
||||
| { 종류: "행넣기" | "행지우기" | "열넣기" | "열지우기"; 시트: string; at: number; 수: number }
|
||||
/** 잘라 붙이기 — 옮긴 칸을 가리키던 식도 따라감 */
|
||||
| { 종류: "옮기기"; 시트: string; 범위: CellRange; 대상시트: string; r: number; c: number }
|
||||
| { 종류: "병합"; 시트: string; 범위: CellRange }
|
||||
| { 종류: "병합풀기"; 시트: string; 범위: CellRange }
|
||||
/** 폭(글자 단위) · 높이(pt) — null = 기본으로 */
|
||||
| { 종류: "열폭"; 시트: string; 열: number[]; 폭: number | null }
|
||||
| { 종류: "행높이"; 시트: string; 행: number[]; 높이: number | null }
|
||||
| { 종류: "숨김"; 시트: string; 축: "행" | "열"; 번호: number[]; 숨김: boolean }
|
||||
| { 종류: "틀고정"; 시트: string; 행: number; 열: number }
|
||||
| { 종류: "눈금선"; 시트: string; 보임: boolean }
|
||||
| { 종류: "시트더하기"; 시트: Sheet; 자리: number }
|
||||
| { 종류: "시트지우기"; 시트: string }
|
||||
/** 이름 바꾸면 다른 시트 식 글의 이름도 바꿈 */
|
||||
| { 종류: "시트이름"; 시트: string; 이름: string }
|
||||
| { 종류: "시트옮기기"; 시트: string; 자리: number }
|
||||
/** 되돌림 짝 전용 — 시트 통째 바꿔 넣기(행열 지우기 되돌림 등) */
|
||||
| { 종류: "시트통째"; 시트: Sheet; 서식?: CellStyle[] }
|
||||
/** 여러 명령 = 되돌리기 한 번 */
|
||||
| { 종류: "묶음"; 명령: Command[] };
|
||||
|
||||
/** 명령을 적용한 뒤 계산 엔진에 알릴 것 — 칸만 바뀌었으면 칸 목록 · 모양이 바뀌었으면 rebuild. */
|
||||
export interface CommandEffect {
|
||||
/** 되돌림 짝 */
|
||||
undo: Command;
|
||||
/** 값 · 식이 바뀐 칸 — 딸린 칸만 다시 풂 */
|
||||
cells: SheetCellAddress[];
|
||||
/** 행열 · 옮기기 · 시트 명령 — 엔진을 통째로 다시 */
|
||||
rebuild: boolean;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// 7. 계산 엔진 — 통합문서를 참조로 쥠(명령이 문서를 바꾸면 알려 줌)
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export interface CalcEngine {
|
||||
/** 바뀐 칸 → 딸린 칸만 다시 풂 · 다시 푼 칸(바뀐 칸 포함) 목록 */
|
||||
update(changed: SheetCellAddress[]): SheetCellAddress[];
|
||||
/** 문서를 통째로 다시 읽고 전부 풂(행열 · 시트 명령 · setDoc 뒤) */
|
||||
rebuild(book: Workbook): void;
|
||||
/** 칸 값 — 식 칸이면 푼 값 · 아니면 입력 값(수는 분수) */
|
||||
value(sheet: string, r: number, c: number): Scalar;
|
||||
/** 식 칸이 가리키는 칸 · 범위(편집기 색 테두리 · 오류 추적) */
|
||||
precedents(sheet: string, r: number, c: number): { 시트: string; 범위: CellRange }[];
|
||||
/** 식 칸 전부 → 저장 모양(서버 `계산값` · 시험 대조) */
|
||||
snapshot(): CalcValues;
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// 8. 되돌리기 · 채우기 · 숫자 형식 · 클립보드 (A · B · E 공개 모양)
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
export interface History {
|
||||
/** 적용이 끝난 명령과 그 되돌림 짝을 쌓음 · 다시 스택은 비움 */
|
||||
push(done: Command, undo: Command): void;
|
||||
undo(): Command | null;
|
||||
redo(): Command | null;
|
||||
canUndo(): boolean;
|
||||
canRedo(): boolean;
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
export type FillDirection = "down" | "up" | "right" | "left";
|
||||
|
||||
/** 숫자 형식 결과 — `색` 은 `[Red]` 같은 형식 색 · `채움글` 은 `*x` 반복 글(격자가 폭만큼 늘림). */
|
||||
export interface FormattedText {
|
||||
글: string;
|
||||
색?: string;
|
||||
채움글?: string;
|
||||
/** 형식이 정한 정렬(일반 정렬일 때 수 = 오른쪽 · 글 = 왼쪽) */
|
||||
정렬: "left" | "right" | "center";
|
||||
}
|
||||
|
||||
/** 붙여넣기로 읽은 칸 뭉치 — 왼위 기준 상대 자리. */
|
||||
export interface ClipCell {
|
||||
값?: CellInput;
|
||||
/** 앞 `=` 뺀 A1 식 — `원점` 기준으로 적힌 글 */
|
||||
식?: string;
|
||||
서식?: CellStyle;
|
||||
}
|
||||
|
||||
/** 클립보드 형 — 브라우저가 읽는 것은 `text/plain`(TSV) · `text/html` 둘.
|
||||
* 우리 → 밖: 둘 다 씀(보이는 값 + 인라인 서식 표) · 우리 → 우리: HTML 속 data 속성에 뭉치 JSON.
|
||||
* 밖 → 우리 수식: ① 구글 `data-sheets-formula`(R1C1 상대) ② 엑셀 「수식 표시」(Ctrl+`) 로 복사한 글 칸이
|
||||
* `=` 뒤에 글이 있고 식으로 읽히면 식(「=」 한 글자 칸은 글) — 실제 되는지는 E 가 이 PC 엑셀로 시험. */
|
||||
export interface ClipBlock {
|
||||
rows: number;
|
||||
cols: number;
|
||||
/** `r,c` 열쇠(0 부터 · 뭉치 왼위 기준) */
|
||||
칸: Record<string, ClipCell>;
|
||||
/** 뭉치 안 병합(0 부터) */
|
||||
병합: CellRange[];
|
||||
/** 식 글이 적힌 기준 왼위 — 붙일 때 (붙일 자리 − 원점) 만큼 상대 참조를 옮김.
|
||||
* null = 옮기지 않음(엑셀 수식 표시 글 — 적힌 주소 그대로 · 구글 R1C1 은 붙일 자리로 풀어 null). */
|
||||
원점: CellAddress | null;
|
||||
출처: "aislo" | "google" | "excel" | "text";
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_view_types.ts
|
||||
* 화면 부품(C 격자 · D 입력 · E 도구) 사이 계약 — 문맥 하나를 D 가 만들어 C · E 에 넘김.
|
||||
* 0 계약 주인만 고침. 파일 목록 · 주인 · 규칙은 `spreadsheet_types.ts` 머리.
|
||||
*
|
||||
* 흐름: 사용자 조작 → (D · E) `ctx.dispatch(명령)` → A 적용 · 되돌림 쌓기 · 엔진 update/rebuild →
|
||||
* C `grid.invalidate(칸)`(보이는 칸만 다시) → `onChange(문서)`. 서버 왕복 없음 · 자동저장 없음.
|
||||
* ========================================================================== */
|
||||
|
||||
import type {
|
||||
CalcEngine,
|
||||
CellAddress,
|
||||
CellRange,
|
||||
Command,
|
||||
History,
|
||||
Sheet,
|
||||
SheetCellAddress,
|
||||
Workbook,
|
||||
} from "./spreadsheet_types";
|
||||
|
||||
/** 고름 — 범위 여럿(Ctrl+누르기) · 활성 칸은 편집 · 수식 입력줄이 보이는 칸 · 기준은 Shift 늘림의 닻. */
|
||||
export interface Selection {
|
||||
시트: string;
|
||||
범위: CellRange[];
|
||||
활성: CellAddress;
|
||||
기준: CellAddress;
|
||||
}
|
||||
|
||||
/** 격자 안 한 점이 가리키는 것 — 머리 끝(`colEdge` · `rowEdge`)은 폭 · 높이 끌기 자리. */
|
||||
export interface HitResult {
|
||||
kind: "cell" | "colHead" | "rowHead" | "corner" | "colEdge" | "rowEdge" | "fillHandle";
|
||||
r: number;
|
||||
c: number;
|
||||
}
|
||||
|
||||
/** 칸 자리 — 격자 내용 좌표(px · 스크롤 전) · 병합이면 병합 전체. */
|
||||
export interface CellBox {
|
||||
x: number;
|
||||
y: number;
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
/** 편집 중 식 참조 색 테두리(D 가 채우고 C 가 그림). */
|
||||
export interface RefHighlight {
|
||||
시트: string;
|
||||
범위: CellRange;
|
||||
/** 0 부터 차례 — 색은 C 가 차례로 고름 */
|
||||
색번호: number;
|
||||
}
|
||||
|
||||
/** C 격자 — `createGrid(ctx)` 가 돌려줌. */
|
||||
export interface GridHandle {
|
||||
/** 격자 뿌리(스크롤 판 · 틀고정 판 포함) — D 가 창 안에 붙임 */
|
||||
root: HTMLElement;
|
||||
/** 보이는 칸 통째로 다시(시트 바꿈 · 행열 · 폭 · 틀고정 뒤) */
|
||||
render(): void;
|
||||
/** 값 · 서식이 바뀐 칸만 다시(보이는 것만) */
|
||||
invalidate(cells: SheetCellAddress[]): void;
|
||||
/** 고름 · 참조 색 테두리 · 채우기 손잡이 다시 그림 */
|
||||
renderSelection(): void;
|
||||
setRefHighlights(list: RefHighlight[]): void;
|
||||
cellBox(r: number, c: number): CellBox;
|
||||
/** 화면 좌표 → 가리킨 것(격자 밖 null) */
|
||||
hitTest(clientX: number, clientY: number): HitResult | null;
|
||||
/** 칸이 보이게 스크롤(틀고정 판이면 그대로) */
|
||||
reveal(r: number, c: number): void;
|
||||
/** 지금 보이는 행열(틀고정 판 뺀 본문 판) */
|
||||
visibleRange(): CellRange;
|
||||
/** 편집기를 얹을 층과 칸 자리(화면 좌표 · 병합이면 전체) — D 편집기가 씀 */
|
||||
editorSlot(r: number, c: number): { layer: HTMLElement; box: CellBox };
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
/** D 편집기 — E 도구 모음 · C 가 부름. */
|
||||
export interface EditorHandle {
|
||||
/** 편집 중인가 — 방향키 · 붙여넣기 뜻이 달라짐 */
|
||||
editing(): boolean;
|
||||
/** 활성 칸 편집 시작(`initial` = 친 글자로 시작) */
|
||||
begin(initial?: string): void;
|
||||
/** 확정 · 취소 */
|
||||
commit(): void;
|
||||
cancel(): void;
|
||||
/** 수식 입력줄 · 주소 상자를 활성 칸으로 다시 */
|
||||
sync(): void;
|
||||
}
|
||||
|
||||
/** D 가 만들어 C · E 에 넘기는 문맥 — 문서는 이것 하나를 여럿이 봄. */
|
||||
export interface SpreadsheetContext {
|
||||
/** 부품 뿌리 */
|
||||
root: HTMLElement;
|
||||
/** 지금 문서 — 명령만이 바꿈(직접 고치지 말 것) */
|
||||
book: Workbook;
|
||||
readOnly: boolean;
|
||||
engine: CalcEngine;
|
||||
history: History;
|
||||
selection: Selection;
|
||||
/** 활성 시트 */
|
||||
sheet(): Sheet;
|
||||
/** 명령 하나 적용 → 되돌림 쌓기 → 엔진 → 다시 그림 → onChange. readOnly 면 안 함 */
|
||||
dispatch(command: Command): void;
|
||||
undo(): void;
|
||||
redo(): void;
|
||||
/** 고름 바꿈 → 다시 그림 · 수식 입력줄 · onSelect */
|
||||
select(selection: Selection, reveal?: boolean): void;
|
||||
/** 활성 시트 바꿈(탭) */
|
||||
showSheet(sheetId: string): void;
|
||||
grid: GridHandle;
|
||||
editor: EditorHandle;
|
||||
}
|
||||
|
||||
/** E 부품 — D 가 `mountToolbar(ctx)` 등으로 붙이고 고름 · 문서가 바뀌면 `refresh()`. */
|
||||
export interface PartHandle {
|
||||
root: HTMLElement | null;
|
||||
refresh(): void;
|
||||
destroy(): void;
|
||||
}
|
||||
Reference in New Issue
Block a user