perf(spreadsheet): D 크기 줄임 — sub2 · sub8 병합 뒤 화면 예산 넘침 해소
- 함수 자동 완성 · 인자 도움말, 이름 상자 · 이름 관리, 서식 붓, 메모 편집(새 spreadsheet_memo.ts), 열 너비 맞춤을 처음 쓸 때 불러옴 - 화면 30,126 B → 28,102 B(예산 28,672) · 합 62.6 KB Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L6KXAabDTenEU7hrDQmCKY
This commit is contained in:
@@ -17,7 +17,6 @@ import { autoSumRange } from "./spreadsheet_autosum";
|
||||
import { isError } from "./spreadsheet_eval";
|
||||
import type { FilterHandle } from "./spreadsheet_filter";
|
||||
import { createCondFmtPanel } from "./spreadsheet_condfmt";
|
||||
import { createNameManagerPanel } from "./spreadsheet_names";
|
||||
import { createSheetExtraPanel } from "./spreadsheet_sheet_extra";
|
||||
import { mountActions } from "./spreadsheet_actions";
|
||||
import type { ErrorTipHandle } from "./spreadsheet_errcheck";
|
||||
@@ -174,7 +173,14 @@ export function mountDataMenu(ctx: SpreadsheetContext, back: () => void): DataMe
|
||||
[TEXT.filterItem, () => (toggleFilter(), back())],
|
||||
[TEXT.condfmt, () => void openPanel(TEXT.condfmt, async () => createCondFmtPanel)],
|
||||
[TEXT.validation, () => void openPanel(TEXT.validation, async () => createValidationPanel)],
|
||||
[TEXT.names, () => void openPanel(TEXT.names, async () => createNameManagerPanel)],
|
||||
[
|
||||
TEXT.names,
|
||||
() =>
|
||||
void openPanel(
|
||||
TEXT.names,
|
||||
async () => (await import("./spreadsheet_names")).createNameManagerPanel,
|
||||
),
|
||||
],
|
||||
[TEXT.sheets, () => void openPanel(TEXT.sheets, async () => createSheetExtraPanel)],
|
||||
[TEXT.grid, () => (toggleGridlines(), back())],
|
||||
...act.items,
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
import { el, showToast } from "@ui/ui_template_elements";
|
||||
import { moveFormula } from "./spreadsheet_refshift";
|
||||
import { colName, parseA1, parseRange, rangeToA1, toA1 } from "./spreadsheet_address";
|
||||
import { funcHelp, suggestFunctions, type FuncHelp } from "./spreadsheet_func_help";
|
||||
import { defineName, resolveName } from "./spreadsheet_names";
|
||||
import type { FuncHelp } from "./spreadsheet_func_help";
|
||||
import { parseFormula, tokenize } from "./spreadsheet_parser";
|
||||
import { cellsOf, move, selectCell } from "./spreadsheet_selection";
|
||||
import { checkValue, findValidation } from "./spreadsheet_validation";
|
||||
@@ -284,13 +283,15 @@ export function createEditor(ctx: SpreadsheetContext): Editor {
|
||||
let sugg: FuncHelp[] = [];
|
||||
let pick = 0;
|
||||
let typed = "";
|
||||
let fh: typeof import("./spreadsheet_func_help") | null = null; // 식을 처음 칠 때 불러옴 — 크기 예산
|
||||
|
||||
function showHint(): void {
|
||||
const node = src();
|
||||
const h = st ? formulaHint(node.value.slice(0, node.selectionEnd)) : null;
|
||||
sugg = h && "prefix" in h ? suggestFunctions(h.prefix).slice(0, 8) : [];
|
||||
if (h && !fh) return void import("./spreadsheet_func_help").then((m) => ((fh = m), showHint()));
|
||||
sugg = h && "prefix" in h ? fh!.suggestFunctions(h.prefix).slice(0, 8) : [];
|
||||
typed = h && "prefix" in h ? h.prefix : "";
|
||||
const help = h && "func" in h ? funcHelp(h.func) : null;
|
||||
const help = h && "func" in h ? fh!.funcHelp(h.func) : null;
|
||||
if (!sugg.length && !help) return void hint.setAttribute("hidden", "");
|
||||
pick = Math.min(pick, Math.max(0, sugg.length - 1));
|
||||
hint.replaceChildren(
|
||||
@@ -489,7 +490,8 @@ export function createEditor(ctx: SpreadsheetContext): Editor {
|
||||
});
|
||||
|
||||
/** 이름 상자(sub4) — 있는 이름이면 그 범위로 · 없으면 지금 고름에 이름을 붙임(엑셀) */
|
||||
function nameBox(name: string): void {
|
||||
async function nameBox(name: string): Promise<void> {
|
||||
const { defineName, resolveName } = await import("./spreadsheet_names"); // 늦게 — 크기 예산
|
||||
const hit = resolveName(ctx.book, ctx.selection.시트, name);
|
||||
if (hit) {
|
||||
if (hit.시트 !== ctx.selection.시트) ctx.showSheet(hit.시트);
|
||||
@@ -514,7 +516,7 @@ export function createEditor(ctx: SpreadsheetContext): Editor {
|
||||
const body = value.slice(bang + 1).replace(/\$/g, "");
|
||||
const one = parseA1(body);
|
||||
const g = parseRange(body);
|
||||
if (!g) return nameBox(value.trim());
|
||||
if (!g) return void nameBox(value.trim());
|
||||
if (one) return ctx.select(selectCell(sheet, one.r, one.c), true);
|
||||
const at = { r: g.r0, c: g.c0 };
|
||||
ctx.select({ 시트: sheet.id, 범위: [g], 활성: at, 기준: { ...at } }, true);
|
||||
|
||||
@@ -13,8 +13,9 @@ import { attachComments, getComment, setComment } from "./spreadsheet_comments";
|
||||
import { mountDataMenu } from "./spreadsheet_data_menu";
|
||||
import type { Editor } from "./spreadsheet_editor";
|
||||
import type { FindPanelHandle } from "./spreadsheet_find_panel";
|
||||
import { createFormatPainter } from "./spreadsheet_format_painter";
|
||||
import type { FormatPainterHandle } from "./spreadsheet_format_painter";
|
||||
import type { KeyHooks } from "./spreadsheet_keys";
|
||||
import type { Memo } from "./spreadsheet_memo";
|
||||
import type { MouseHooks } from "./spreadsheet_mouse";
|
||||
import type { PasteSpecialMode } from "./spreadsheet_paste_special";
|
||||
import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types";
|
||||
@@ -24,7 +25,6 @@ const TEXT = {
|
||||
painter: "서식 붓 — 한 번 누르면 한 번 · 두 번 누르면 Esc 까지 계속",
|
||||
pasteTitle: "골라 붙여넣기",
|
||||
modes: { 값: "값만", 서식: "서식만", 수식: "수식만" } as Record<PasteSpecialMode, string>,
|
||||
memo: "메모 — Ctrl+Enter 또는 밖을 누르면 적용 · Esc 취소",
|
||||
};
|
||||
|
||||
export interface Extras {
|
||||
@@ -62,40 +62,20 @@ export function attachExtras(
|
||||
// ── 메모 — 풍선 · 편집 ─────────────────────────────────────────────────
|
||||
const comments = attachComments(ctx);
|
||||
let hovered = "";
|
||||
const memo = el("textarea", { className: "ss-memo", attrs: { hidden: "", title: TEXT.memo } });
|
||||
ctx.root.append(memo);
|
||||
let memoAt: { r: number; c: number } | null = null;
|
||||
const closeMemo = (save: boolean): void => {
|
||||
if (!memoAt) return;
|
||||
const at = memoAt;
|
||||
memoAt = null;
|
||||
memo.setAttribute("hidden", "");
|
||||
if (save && memo.value !== (getComment(ctx.sheet(), at.r, at.c) ?? ""))
|
||||
setComment(ctx, at.r, at.c, memo.value.trim());
|
||||
back();
|
||||
};
|
||||
memo.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" || (e.key === "Enter" && e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closeMemo(e.key === "Enter");
|
||||
}
|
||||
});
|
||||
memo.addEventListener("blur", () => closeMemo(true));
|
||||
|
||||
function editMemo(): void {
|
||||
let memo: Memo | null = null; // 처음 쓸 때 불러옴 — 크기 예산
|
||||
async function editMemo(): Promise<void> {
|
||||
if (ctx.readOnly) return;
|
||||
const { r, c } = ctx.selection.활성;
|
||||
memoAt = { r, c };
|
||||
memo ??= (await import("./spreadsheet_memo")).createMemo(ctx, back, {
|
||||
get: getComment,
|
||||
set: setComment,
|
||||
});
|
||||
comments.hideBalloon();
|
||||
memo.value = getComment(ctx.sheet(), r, c) ?? "";
|
||||
memo.removeAttribute("hidden");
|
||||
put(memo, r, c);
|
||||
memo.focus();
|
||||
memo.open(r, c, (node) => put(node, r, c));
|
||||
}
|
||||
|
||||
// ── 서식 붓 ─────────────────────────────────────────────────────────────
|
||||
const painter = createFormatPainter(ctx);
|
||||
let painter: FormatPainterHandle | null = null; // 처음 누를 때 불러옴 — 크기 예산
|
||||
let sticky = false;
|
||||
const brush = el("button", {
|
||||
className: "ss-brush",
|
||||
@@ -103,10 +83,12 @@ export function attachExtras(
|
||||
attrs: { type: "button", title: TEXT.painter },
|
||||
});
|
||||
const showBrush = (): void => {
|
||||
brush.classList.toggle("is-on", painter.active());
|
||||
ctx.root.classList.toggle("ss--painting", painter.active());
|
||||
const on = !!painter?.active();
|
||||
brush.classList.toggle("is-on", on);
|
||||
ctx.root.classList.toggle("ss--painting", on);
|
||||
};
|
||||
brush.addEventListener("click", (e) => {
|
||||
brush.addEventListener("click", async (e) => {
|
||||
painter ??= (await import("./spreadsheet_format_painter")).createFormatPainter(ctx);
|
||||
if (e.detail === 2) sticky = true;
|
||||
else if (painter.active()) (painter.clear(), (sticky = false));
|
||||
else (painter.pick(), (sticky = false));
|
||||
@@ -198,7 +180,7 @@ export function attachExtras(
|
||||
toolbarRoot?.removeEventListener("click", onToolbarClick);
|
||||
toolbarRoot?.removeEventListener("change", back);
|
||||
find?.destroy();
|
||||
memo.remove();
|
||||
memo?.destroy();
|
||||
picker.remove();
|
||||
brush.remove();
|
||||
fmt.remove();
|
||||
@@ -209,18 +191,18 @@ export function attachExtras(
|
||||
keys: {
|
||||
find: (replace) => void openFind(replace),
|
||||
pasteSpecial: () => (armed = true),
|
||||
comment: editMemo,
|
||||
comment: () => void editMemo(),
|
||||
formatDialog: () => void openFormat(),
|
||||
...data.keys,
|
||||
escape() {
|
||||
painter.clear();
|
||||
painter?.clear();
|
||||
sticky = false;
|
||||
showBrush();
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
selected() {
|
||||
if (!painter.active()) return;
|
||||
if (!painter?.active()) return;
|
||||
painter.paint(ctx.selection.범위[0], sticky);
|
||||
showBrush();
|
||||
},
|
||||
@@ -228,7 +210,7 @@ export function attachExtras(
|
||||
const key = hit?.kind === "cell" ? `${hit.r},${hit.c}` : "";
|
||||
if (key === hovered) return;
|
||||
hovered = key;
|
||||
if (!hit || !key || memoAt || !getComment(ctx.sheet(), hit.r, hit.c))
|
||||
if (!hit || !key || memo?.busy() || !getComment(ctx.sheet(), hit.r, hit.c))
|
||||
return comments.hideBalloon();
|
||||
comments.showBalloon(hit.r, hit.c);
|
||||
put(comments.root!, hit.r, hit.c); // showBalloon 의 cellBox 자리 → 화면 자리로 고침
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/* =============================================================================
|
||||
* spreadsheet_memo.ts (주인 D)
|
||||
* 메모 편집(Shift+F2) — 칸 옆 글상자 · Ctrl+Enter 또는 밖을 누르면 적용 · Esc 취소.
|
||||
* 처음 쓸 때 `spreadsheet_extras.ts` 가 불러옴(크기 예산).
|
||||
* ========================================================================== */
|
||||
|
||||
import { el } from "@ui/ui_template_elements";
|
||||
import type { getComment, setComment } from "./spreadsheet_comments";
|
||||
import type { SpreadsheetContext } from "./spreadsheet_view_types";
|
||||
|
||||
const TITLE = "메모 — Ctrl+Enter 또는 밖을 누르면 적용 · Esc 취소";
|
||||
|
||||
export interface Memo {
|
||||
/** (r, c) 메모를 열고 `place` 로 칸 옆에 둠 */
|
||||
open(r: number, c: number, place: (node: HTMLElement) => void): void;
|
||||
/** 편집 중인가 — 풍선을 띄우지 않음 */
|
||||
busy(): boolean;
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
/** 메모 읽기 · 쓰기는 부른 쪽이 넘김 — 여기서 끌어오면 메모 조각이 따로 쪼개져 크기가 늘어남 */
|
||||
export function createMemo(
|
||||
ctx: SpreadsheetContext,
|
||||
back: () => void,
|
||||
io: { get: typeof getComment; set: typeof setComment },
|
||||
): Memo {
|
||||
const memo = el("textarea", { className: "ss-memo", attrs: { hidden: "", title: TITLE } });
|
||||
ctx.root.append(memo);
|
||||
let memoAt: { r: number; c: number } | null = null;
|
||||
const close = (save: boolean): void => {
|
||||
if (!memoAt) return;
|
||||
const at = memoAt;
|
||||
memoAt = null;
|
||||
memo.setAttribute("hidden", "");
|
||||
if (save && memo.value !== (io.get(ctx.sheet(), at.r, at.c) ?? ""))
|
||||
io.set(ctx, at.r, at.c, memo.value.trim());
|
||||
back();
|
||||
};
|
||||
memo.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" || (e.key === "Enter" && e.ctrlKey)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
close(e.key === "Enter");
|
||||
}
|
||||
});
|
||||
memo.addEventListener("blur", () => close(true));
|
||||
|
||||
return {
|
||||
open(r, c, place) {
|
||||
memoAt = { r, c };
|
||||
memo.value = io.get(ctx.sheet(), r, c) ?? "";
|
||||
memo.removeAttribute("hidden");
|
||||
place(memo);
|
||||
memo.focus();
|
||||
},
|
||||
busy: () => !!memoAt,
|
||||
destroy: () => memo.remove(),
|
||||
};
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
* 머리 끝(폭 · 높이 끌기)은 C 몫 — 여기서 건드리지 않음.
|
||||
* ========================================================================== */
|
||||
|
||||
import { autofitColumn } from "./spreadsheet_autofit";
|
||||
import { fillCells } from "./spreadsheet_fill";
|
||||
import {
|
||||
hasValue,
|
||||
@@ -242,7 +241,10 @@ export function attachMouse(
|
||||
// 고른 열 전체의 경계면 고른 열 모두(엑셀)
|
||||
const g = ctx.selection.범위[0];
|
||||
const whole = g.r0 === 0 && g.r1 === LAST_R && inRange(g, 0, hit.c);
|
||||
for (let c = whole ? g.c0 : hit.c; c <= (whole ? g.c1 : hit.c); c++) autofitColumn(ctx, c);
|
||||
void import("./spreadsheet_autofit").then((m) => {
|
||||
for (let c = whole ? g.c0 : hit.c; c <= (whole ? g.c1 : hit.c); c++)
|
||||
m.autofitColumn(ctx, c);
|
||||
}); // 늦게 — 크기 예산
|
||||
return;
|
||||
}
|
||||
if (hit.kind === "cell" && !ed.editing()) ed.begin();
|
||||
|
||||
Reference in New Issue
Block a user