Merge remote-tracking branches 'origin/sub_laptop_2' and 'origin/sub_laptop_8' into sub_laptop_4

This commit is contained in:
2026-09-28 12:45:10 +09:00
4 changed files with 178 additions and 2 deletions
@@ -95,6 +95,14 @@ const TEXT = {
"병합된 셀의 일부를 바꿀 수 없습니다",
"Can't change part of a merged cell",
],
Unit: ["단위", "Unit"],
UnitGroup_area: ["넓이·부피·길이", "Area · volume · length"],
UnitGroup_weight: ["무게·부피(액체)", "Weight · liquid volume"],
UnitGroup_count: ["개수", "Count"],
UnitGroup_rate: ["속도·각도·비율", "Speed · angle · rate"],
UnitGroup_op: ["연산·비교", "Operators"],
UnitRecent: ["최근 씀", "Recently used"],
UnitAsFormat: ["숫자 형식으로 붙이기", "Attach as number format"],
} as const;
export function st(key: keyof typeof TEXT): string {
@@ -119,6 +119,32 @@
gap: var(--spacing-4);
}
.ss-toolbar__unit-pop {
max-height: 340px;
overflow-y: auto;
}
.ss-toolbar__unit-section + .ss-toolbar__unit-section {
margin-top: var(--spacing-8);
}
.ss-toolbar__unit-label {
margin-bottom: var(--spacing-4);
color: var(--color-text-muted);
font-size: 0.72rem;
}
.ss-toolbar__unit-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: var(--spacing-4);
}
.ss-toolbar__unit-btn {
min-width: 0;
padding: 4px 2px;
}
/* ── 시트 탭 ─────────────────────────────────────────────────────────────── */
.ss-tabs {
@@ -12,6 +12,7 @@
import { createButton, el } from "@ui/ui_template_elements";
import { colName, rangeToA1, toA1 } from "./spreadsheet_address";
import { cellToText, textToCell } from "./spreadsheet_editor";
import { st } from "./spreadsheet_text";
import type { BorderLine, BorderSide, CellRange, CellStyle, Command } from "./spreadsheet_types";
import type { PartHandle, SpreadsheetContext } from "./spreadsheet_view_types";
@@ -58,6 +59,43 @@ const BORDER_LINES: BorderLine[] = [
"dashed",
];
type TextKey = Parameters<typeof st>[0];
const UNIT_GROUPS: [label: TextKey, items: string[]][] = [
["UnitGroup_area", ["㎡", "㎥", "m", "㎝", "㎜", "㎞"]],
["UnitGroup_weight", ["kg", "t", "ℓ", "㎖"]],
["UnitGroup_count", ["개", "본", "식", "EA", "매"]],
["UnitGroup_rate", ["m/s", "°", "%", "‰"]],
["UnitGroup_op", ["×", "÷", "±", "≤", "≥", "Σ"]],
];
const UNIT_RECENT_MAX = 8;
const UNIT_RECENT_KEY = "aislo-ss-recent-units";
function loadRecentUnits(): string[] {
try {
const raw = localStorage.getItem(UNIT_RECENT_KEY);
return raw ? (JSON.parse(raw) as string[]) : [];
} catch {
return [];
}
}
function pushRecentUnit(unit: string): string[] {
const list = [unit, ...loadRecentUnits().filter((u) => u !== unit)].slice(0, UNIT_RECENT_MAX);
try {
localStorage.setItem(UNIT_RECENT_KEY, JSON.stringify(list));
} catch {
/* 저장 못 해도 이번 넣기는 그대로 진행 */
}
return list;
}
/** 숫자 형식 코드 끝 따옴표 글자(전에 붙인 단위)를 떼고 새 단위를 붙임 — 없으면 "0" 에서 시작. */
function attachUnitToFormat(code: string, unit: string): string {
const base = (code || "0").replace(/"[^"]*"\s*$/, "");
return base + `"${unit}"`;
}
/** 활성 칸에 지금 걸린 서식 — 칸 자기 것 없으면 열 · 행 · 시트 기본(`Sheet.기본.서식`) · 그다음 0. */
function styleAt(ctx: SpreadsheetContext, r: number, c: number): CellStyle {
const sheet = ctx.sheet();
@@ -332,6 +370,88 @@ export function mountToolbar(ctx: SpreadsheetContext): PartHandle {
numfmtInput.append(numfmtList);
numfmtInput.addEventListener("change", () => patch({ 형식: numfmtInput.value || null }));
// ── 단위 기호 ─────────────────────────────────────────────────────────
const unitBtn = createButton({ label: `${st("Unit")} ▾`, variant: "ghost" });
const unitPop = el("div", {
className: "ss-toolbar__popover ss-toolbar__unit-pop",
attrs: { hidden: "true" },
});
const unitAsFormat = el("input", { attrs: { type: "checkbox", id: "ss-unit-as-format" } });
const unitAsFormatRow = el("label", {
className: "ss-toolbar__popover-row",
attrs: { for: "ss-unit-as-format" },
children: [unitAsFormat, el("span", { text: st("UnitAsFormat") })],
});
const recentGrid = el("div", { className: "ss-toolbar__unit-grid" });
const recentSection = el("div", {
className: "ss-toolbar__unit-section",
attrs: { hidden: "true" },
children: [
el("div", { className: "ss-toolbar__unit-label", text: st("UnitRecent") }),
recentGrid,
],
});
function insertUnit(unit: string): void {
if (ctx.editor.editing()) {
const active = document.activeElement;
if (active instanceof HTMLTextAreaElement && ctx.root.contains(active)) {
const s = active.selectionStart ?? active.value.length;
const e = active.selectionEnd ?? active.value.length;
active.value = active.value.slice(0, s) + unit + active.value.slice(e);
active.setSelectionRange(s + unit.length, s + unit.length);
active.dispatchEvent(new Event("input", { bubbles: true }));
}
} else if (unitAsFormat.checked) {
const { r, c } = ctx.selection.활성;
patch({ 형식: attachUnitToFormat(styleAt(ctx, r, c).형식 ?? "", unit) });
} else {
const { r, c } = ctx.selection.활성;
const sheet = ctx.sheet();
const key = toA1(r, c);
const text = cellToText(sheet.칸[key]) + unit;
ctx.dispatch({ 종류: "칸", 시트: sheet.id, 칸: { [key]: textToCell(text, sheet.칸[key]) } });
}
renderRecent(pushRecentUnit(unit));
unitPop.hidden = true;
}
function renderRecent(list: string[]): void {
recentGrid.textContent = "";
recentSection.hidden = list.length === 0;
for (const u of list) {
const b = createButton({ label: u, variant: "ghost", onClick: () => insertUnit(u) });
b.className += " ss-toolbar__unit-btn";
recentGrid.append(b);
}
}
renderRecent(loadRecentUnits());
unitPop.append(unitAsFormatRow, recentSection);
for (const [labelKey, items] of UNIT_GROUPS) {
const grid = el("div", { className: "ss-toolbar__unit-grid" });
for (const u of items) {
const b = createButton({ label: u, variant: "ghost", onClick: () => insertUnit(u) });
b.className += " ss-toolbar__unit-btn";
grid.append(b);
}
unitPop.append(
el("div", {
className: "ss-toolbar__unit-section",
children: [el("div", { className: "ss-toolbar__unit-label", text: st(labelKey) }), grid],
}),
);
}
unitBtn.addEventListener("click", (ev) => {
ev.stopPropagation();
unitPop.hidden = !unitPop.hidden;
});
unitPop.addEventListener("click", (ev) => ev.stopPropagation());
const closeUnitPop = (): void => {
unitPop.hidden = true;
};
document.addEventListener("click", closeUnitPop);
root.append(
group(undoBtn, redoBtn),
group(fontInput, sizeInput),
@@ -341,6 +461,7 @@ export function mountToolbar(ctx: SpreadsheetContext): PartHandle {
group(mergeBtn),
group(borderBtn, borderPop),
group(numfmtInput),
group(unitBtn, unitPop),
);
function refresh(): void {
@@ -383,6 +504,7 @@ export function mountToolbar(ctx: SpreadsheetContext): PartHandle {
refresh,
destroy() {
document.removeEventListener("click", closeBorderPop);
document.removeEventListener("click", closeUnitPop);
root.remove();
},
};