fix(m02): 셀 밀기 · 당기기에 이름 따라감 · 카드 도면 저장도 표 없는 연결표 줄 뺌(114-2)

- 칸 삽입 · 삭제 창(밀기 · 당기기) — 띠 안에 통째로 든 이름정의 범위를 같이 옮김 · 되돌리기 한 걸음
- 연결표 맞춤 몸을 fitLinks 로 떼어 도면 상세 · 카드 도면 저장이 같이 씀
- 시험 — 밀기 여섯 갈래 · 카드 저장 줄 빼기

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
2026-10-08 18:28:48 +09:00
co-authored by Claude Opus 5.5
parent f44789c33c
commit ad511175bf
5 changed files with 133 additions and 24 deletions
@@ -18,6 +18,8 @@
import { createButton, el } from "@ui/ui_template_elements";
import { dialogKeys } from "./spreadsheet_actions";
import { parseA1, toA1 } from "./spreadsheet_address";
import { shiftSpan } from "./spreadsheet_refshift";
import { MAX_COLS, MAX_ROWS } from "./spreadsheet_types";
import type { Cell, CellLink, CellRange, Command, Sheet } from "./spreadsheet_types";
import type { SpreadsheetContext } from "./spreadsheet_view_types";
@@ -73,7 +75,7 @@ function shiftLinks(
const along = axis === "col" ? p.r : p.c;
const cross = axis === "col" ? p.c : p.r;
if (along < lo || along > hi || cross < at) return [link]; // 범위 밖 · 밀리는 자리 앞 — 그대로
if (dir < 0 && cross < at + amount) return (changed = true), []; // 지운 자리 — 빠짐
if (dir < 0 && cross < at + amount) return ((changed = true), []); // 지운 자리 — 빠짐
changed = true;
const moved = cross + dir * amount;
return [{ ...link, 칸: axis === "col" ? toA1(p.r, moved) : toA1(moved, p.c) }];
@@ -81,7 +83,42 @@ function shiftLinks(
return changed ? next : undefined;
}
/** 「칸」 명령(값만) 앞에 — 알약이 움직였으면 「시트통째」(연결만 바꿈)를 한 묶음으로(되돌리기 한 걸음) */
/** 밀기 · 당기기에 이름정의 범위도 따라감(114-2 · 보낸 범위 이름 등) — 이름 범위가 `axis` 범위(띠) 안에
* 통째로 들 때만 옮김(띠에 걸치기만 하면 그대로 · 엑셀과 같음) · 다 지운 자리면 빠짐. 안 바뀌면 `undefined`. */
function shiftNames(
sheet: Sheet,
range: CellRange,
axis: "col" | "row",
dir: 1 | -1,
amount: number,
): Sheet["이름정의"] | undefined {
const list = sheet.이름정의;
if (!list?.length) return undefined;
const col = axis === "col";
const [lo, hi] = col ? [range.r0, range.r1] : [range.c0, range.c1];
let changed = false;
const next = list.flatMap((n) => {
const g = n.범위;
if ((col ? g.r0 : g.c0) < lo || (col ? g.r1 : g.c1) > hi) return [n];
const [a, b] = col ? [g.c0, g.c1] : [g.r0, g.r1];
const to = shiftSpan(
a,
b,
dir > 0,
col ? range.c0 : range.r0,
amount,
col ? MAX_COLS : MAX_ROWS,
);
if (to && to[0] === a && to[1] === b) return [n];
changed = true;
if (!to) return [];
return [{ ...n, 범위: col ? { ...g, c0: to[0], c1: to[1] } : { ...g, r0: to[0], r1: to[1] } }];
});
return changed ? next : undefined;
}
/** 「칸」 명령(값만) 앞에 — 알약 · 이름이 움직였으면 「시트통째」(연결 · 이름정의만 바꿈)를 한 묶음으로
* (되돌리기 한 걸음) */
function dispatchShift(
ctx: SpreadsheetContext,
sheet: Sheet,
@@ -93,10 +130,14 @@ function dispatchShift(
): void {
const cellCmd: Command = { 종류: "칸", 시트: ctx.selection.시트, 칸: cells };
const linked = shiftLinks(sheet, range, axis, dir, amount);
const named = shiftNames(sheet, range, axis, dir, amount);
const next = {
...sheet,
...(linked ? { 연결: linked } : {}),
...(named ? { 이름정의: named } : {}),
};
ctx.dispatch(
linked
? { 종류: "묶음", 명령: [{ 종류: "시트통째", 시트: { ...sheet, 연결: linked } }, cellCmd] }
: cellCmd,
linked || named ? { 종류: "묶음", 명령: [{ 종류: "시트통째", 시트: next }, cellCmd] } : cellCmd,
);
}