fix(spreadsheet): 셀 밀기·당기기도 알약 연결 따라감, 행열 넣기·지우기에 조건부서식·유효성 범위 같이 옮김(104-2)
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MPcuokNm1ZR2kqgGGDfaJA
This commit is contained in:
@@ -34,6 +34,8 @@ import type {
|
||||
CellStyle,
|
||||
Command,
|
||||
CommandEffect,
|
||||
CondFmtRule,
|
||||
DataValidationRule,
|
||||
Sheet,
|
||||
SheetCellAddress,
|
||||
Workbook,
|
||||
@@ -310,6 +312,15 @@ function insertDelete(
|
||||
const 범위 = shiftRange(d.범위);
|
||||
return 범위 ? [{ ...d, 범위 }] : [];
|
||||
});
|
||||
// 조건부서식 · 유효성 범위도 같이(104-2) — `remapCondFmtRanges`·`remapValidationRanges`(3단계) 와
|
||||
// 같은 규칙이지만 그 모듈을 안 끌어옴(기본 번들 크기 예산 `test_spreadsheet_size`) — 여기 그대로 적음.
|
||||
const remapRanges = <T extends { 범위: CellRange[] }>(rules: T[]): T[] =>
|
||||
rules.flatMap((rule) => {
|
||||
const 범위 = rule.범위.map(shiftRange).filter((r): r is CellRange => r !== null);
|
||||
return 범위.length ? [{ ...rule, 범위 }] : [];
|
||||
});
|
||||
if (s.조건부서식) s.조건부서식 = remapRanges<CondFmtRule>(s.조건부서식);
|
||||
if (s.유효성) s.유효성 = remapRanges<DataValidationRule>(s.유효성);
|
||||
|
||||
const axis = rows ? "행" : "열";
|
||||
const table = s[axis];
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import { dialogKeys } from "./spreadsheet_actions";
|
||||
import { parseA1, toA1 } from "./spreadsheet_address";
|
||||
import type { Cell, CellRange, Sheet } from "./spreadsheet_types";
|
||||
import type { Cell, CellLink, CellRange, Command, Sheet } from "./spreadsheet_types";
|
||||
import type { SpreadsheetContext } from "./spreadsheet_view_types";
|
||||
|
||||
const TEXT = {
|
||||
@@ -51,6 +51,55 @@ function sheetUsedBounds(sheet: Sheet): { maxR: number; maxC: number } {
|
||||
return { maxR, maxC };
|
||||
}
|
||||
|
||||
/** 밀기 · 당기기 범위 안 알약 연결도 같이 옮김(104-2) — `axis` 범위(행이면 열 범위 · 열이면 행 범위) 안
|
||||
* 알약만 · `dir` +1 밀기(삽입) · -1 당기기(삭제, `amount` 폭만큼 지운 자리의 알약은 빠짐). 안 바뀌면 `undefined`
|
||||
* (되돌리기 짝에 빈 「시트통째」를 안 끼움). */
|
||||
function shiftLinks(
|
||||
sheet: Sheet,
|
||||
range: CellRange,
|
||||
axis: "col" | "row",
|
||||
dir: 1 | -1,
|
||||
amount: number,
|
||||
): CellLink[] | undefined {
|
||||
const list = sheet.연결;
|
||||
if (!list?.length) return undefined;
|
||||
const lo = axis === "col" ? range.r0 : range.c0;
|
||||
const hi = axis === "col" ? range.r1 : range.c1;
|
||||
const at = axis === "col" ? range.c0 : range.r0;
|
||||
let changed = false;
|
||||
const next = list.flatMap((link): CellLink[] => {
|
||||
const p = parseA1(link.칸);
|
||||
if (!p) return [link];
|
||||
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), []; // 지운 자리 — 빠짐
|
||||
changed = true;
|
||||
const moved = cross + dir * amount;
|
||||
return [{ ...link, 칸: axis === "col" ? toA1(p.r, moved) : toA1(moved, p.c) }];
|
||||
});
|
||||
return changed ? next : undefined;
|
||||
}
|
||||
|
||||
/** 「칸」 명령(값만) 앞에 — 알약이 움직였으면 「시트통째」(연결만 바꿈)를 한 묶음으로(되돌리기 한 걸음) */
|
||||
function dispatchShift(
|
||||
ctx: SpreadsheetContext,
|
||||
sheet: Sheet,
|
||||
range: CellRange,
|
||||
axis: "col" | "row",
|
||||
dir: 1 | -1,
|
||||
amount: number,
|
||||
cells: Record<string, Cell | null>,
|
||||
): void {
|
||||
const cellCmd: Command = { 종류: "칸", 시트: ctx.selection.시트, 칸: cells };
|
||||
const linked = shiftLinks(sheet, range, axis, dir, amount);
|
||||
ctx.dispatch(
|
||||
linked
|
||||
? { 종류: "묶음", 명령: [{ 종류: "시트통째", 시트: { ...sheet, 연결: linked } }, cellCmd] }
|
||||
: cellCmd,
|
||||
);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
// 부분 범위 밀기 — 「칸」 명령으로 값 · 식 · 서식 번호만 옮김(위 머리 주석의 한계 참고)
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
@@ -66,7 +115,7 @@ export function insertShiftRight(ctx: SpreadsheetContext, range: CellRange): voi
|
||||
cells[toA1(r, c)] = src >= range.c0 ? (sheet.칸[toA1(r, src)] ?? null) : null;
|
||||
}
|
||||
}
|
||||
ctx.dispatch({ 종류: "칸", 시트: ctx.selection.시트, 칸: cells });
|
||||
dispatchShift(ctx, sheet, range, "col", 1, width, cells);
|
||||
}
|
||||
|
||||
export function insertShiftDown(ctx: SpreadsheetContext, range: CellRange): void {
|
||||
@@ -80,7 +129,7 @@ export function insertShiftDown(ctx: SpreadsheetContext, range: CellRange): void
|
||||
cells[toA1(r, c)] = src >= range.r0 ? (sheet.칸[toA1(src, c)] ?? null) : null;
|
||||
}
|
||||
}
|
||||
ctx.dispatch({ 종류: "칸", 시트: ctx.selection.시트, 칸: cells });
|
||||
dispatchShift(ctx, sheet, range, "row", 1, height, cells);
|
||||
}
|
||||
|
||||
export function deleteShiftLeft(ctx: SpreadsheetContext, range: CellRange): void {
|
||||
@@ -94,7 +143,7 @@ export function deleteShiftLeft(ctx: SpreadsheetContext, range: CellRange): void
|
||||
cells[toA1(r, c)] = src <= lastCol ? (sheet.칸[toA1(r, src)] ?? null) : null;
|
||||
}
|
||||
}
|
||||
ctx.dispatch({ 종류: "칸", 시트: ctx.selection.시트, 칸: cells });
|
||||
dispatchShift(ctx, sheet, range, "col", -1, width, cells);
|
||||
}
|
||||
|
||||
export function deleteShiftUp(ctx: SpreadsheetContext, range: CellRange): void {
|
||||
@@ -108,7 +157,7 @@ export function deleteShiftUp(ctx: SpreadsheetContext, range: CellRange): void {
|
||||
cells[toA1(r, c)] = src <= lastRow ? (sheet.칸[toA1(src, c)] ?? null) : null;
|
||||
}
|
||||
}
|
||||
ctx.dispatch({ 종류: "칸", 시트: ctx.selection.시트, 칸: cells });
|
||||
dispatchShift(ctx, sheet, range, "row", -1, height, cells);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -229,4 +229,22 @@ section("names", () => {
|
||||
eq("통째 지움 → 이름 빠짐", [b.시트[0].이름정의.length, v("B1")], [0, "#NAME?"]);
|
||||
});
|
||||
|
||||
// ── 5. 104-2 — 행 · 열 넣기 · 지우기 때 조건부서식 · 유효성 범위도 같이(기본 번들 안 — remap 을 그대로 옮겨 적음) ──
|
||||
section("condfmt_validation_follow_rowcol", () => {
|
||||
const { C } = require("./helper_spreadsheet_engine.cjs");
|
||||
const b = book({ A1: 1, B1: 2 });
|
||||
b.시트[0].조건부서식 = [
|
||||
{ id: "cf1", 범위: [A.parseRange("A1:A1")], 조건: { 종류: "글포함", 글: "x" }, 스타일: {} },
|
||||
];
|
||||
b.시트[0].유효성 = [
|
||||
{ id: "v1", 범위: [A.parseRange("B1:B1")], 규칙: { 종류: "정수" }, 어기면: "막음", 메시지: "" },
|
||||
];
|
||||
C.applyCommand(b, { 종류: "행넣기", 시트: "s1", at: 0, 수: 1 });
|
||||
eq("조건부서식 — 행 넣은 뒤 범위", A.rangeToA1(b.시트[0].조건부서식[0].범위[0]), "A2:A2");
|
||||
eq("유효성 — 행 넣은 뒤 범위", A.rangeToA1(b.시트[0].유효성[0].범위[0]), "B2:B2");
|
||||
C.applyCommand(b, { 종류: "열지우기", 시트: "s1", at: 0, 수: 1 }); // A 열 지움
|
||||
eq("조건부서식 — 범위를 다 잃으면 규칙째 빠짐", b.시트[0].조건부서식.length, 0);
|
||||
eq("유효성 — B 열이 A 로 당겨짐", A.rangeToA1(b.시트[0].유효성[0].범위[0]), "A2:A2");
|
||||
});
|
||||
|
||||
process.stdout.write(JSON.stringify(results));
|
||||
|
||||
@@ -61,5 +61,40 @@ import { a1, assertEqual, makeBook, makeCtx, makeSheet, v } from "./test_stage2_
|
||||
assertEqual(book.시트[0].칸[a1(2, 0)], undefined, "위로 당김 · 마지막 자리 빔");
|
||||
}
|
||||
|
||||
// 104-2 — 밀기 · 당기기에도 알약 연결(`연결`)이 따라감.
|
||||
// A1=1 B1=2 C1=3 · B1 에 알약 k1 · B1:B1 자리에 오른쪽으로 밀기 → 알약도 C1 로.
|
||||
{
|
||||
const book = makeBook([
|
||||
makeSheet(
|
||||
"s1",
|
||||
{ [a1(0, 0)]: v(1), [a1(0, 1)]: v(2), [a1(0, 2)]: v(3) },
|
||||
{ 연결: [{ 키: "k1", 칸: a1(0, 1) }] },
|
||||
),
|
||||
]);
|
||||
const ctx = makeCtx(book, "s1");
|
||||
insertShiftRight(ctx, { r0: 0, c0: 1, r1: 0, c1: 1 });
|
||||
assertEqual(book.시트[0].연결, [{ 키: "k1", 칸: a1(0, 2) }], "오른쪽 밀기 · 알약 연결도 C1 로");
|
||||
}
|
||||
|
||||
// A1=1 B1=2 C1=3 · A1 · B1 에 알약 둘 · A1:A1 삭제(왼쪽 당김) → A1 알약은 지운 자리라 빠짐 · B1 알약은
|
||||
// A1 로 당겨짐.
|
||||
{
|
||||
const book = makeBook([
|
||||
makeSheet(
|
||||
"s1",
|
||||
{ [a1(0, 0)]: v(1), [a1(0, 1)]: v(2), [a1(0, 2)]: v(3) },
|
||||
{
|
||||
연결: [
|
||||
{ 키: "k1", 칸: a1(0, 0) },
|
||||
{ 키: "k2", 칸: a1(0, 1) },
|
||||
],
|
||||
},
|
||||
),
|
||||
]);
|
||||
const ctx = makeCtx(book, "s1");
|
||||
deleteShiftLeft(ctx, { r0: 0, c0: 0, r1: 0, c1: 0 });
|
||||
assertEqual(book.시트[0].연결, [{ 키: "k2", 칸: a1(0, 0) }], "왼쪽 당김 · 지운 자리 알약은 빠짐 · 나머지는 당겨짐");
|
||||
}
|
||||
|
||||
if (process.exitCode) process.exit(process.exitCode);
|
||||
console.log("spreadsheet_insert_dialog 시험 끝");
|
||||
|
||||
Reference in New Issue
Block a user