feat(spreadsheet): 단위 Φ — 길이 묶음 끝 · 칸 글 · 형식 코드 앞에 붙임(81-5)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MgChuAcyBqUkhHMTXomQC7
This commit is contained in:
@@ -63,12 +63,14 @@ const BORDER_LINES: BorderLine[] = [
|
||||
type TextKey = Parameters<typeof st>[0];
|
||||
|
||||
const UNIT_GROUPS: [label: TextKey, items: string[]][] = [
|
||||
["UnitGroup_area", ["㎡", "㎥", "m", "㎝", "㎜", "㎞"]],
|
||||
["UnitGroup_area", ["㎡", "㎥", "m", "㎝", "㎜", "㎞", "Φ"]],
|
||||
["UnitGroup_weight", ["kg", "t", "ℓ", "㎖"]],
|
||||
["UnitGroup_count", ["개", "본", "식", "EA", "매"]],
|
||||
["UnitGroup_rate", ["m/s", "°", "%", "‰"]],
|
||||
["UnitGroup_op", ["×", "÷", "±", "≤", "≥", "Σ"]],
|
||||
];
|
||||
/** 숫자 앞에 붙는 단위(관 지름 Φ300) — 나머지는 뒤에 붙음 */
|
||||
const UNIT_PREFIX = ["Φ"];
|
||||
const UNIT_RECENT_MAX = 8;
|
||||
const UNIT_RECENT_KEY = "aislo-ss-recent-units";
|
||||
|
||||
@@ -91,10 +93,14 @@ function pushRecentUnit(unit: string): string[] {
|
||||
return list;
|
||||
}
|
||||
|
||||
/** 숫자 형식 코드 끝 따옴표 글자(전에 붙인 단위)를 떼고 새 단위를 붙임 — 없으면 "0" 에서 시작. */
|
||||
/** 숫자 형식 코드 끝 따옴표 글자(전에 붙인 단위)를 떼고 새 단위를 붙임 — 없으면 "0" 에서 시작.
|
||||
* 앞 단위(Φ)는 코드 앞에 붙임(`"Φ"0`) — 앞에 붙은 앞 단위는 먼저 뗌. */
|
||||
function attachUnitToFormat(code: string, unit: string): string {
|
||||
const base = (code || "0").replace(/"[^"]*"\s*$/, "");
|
||||
return base + `"${unit}"`;
|
||||
const base = code || "0";
|
||||
if (!UNIT_PREFIX.includes(unit)) return base.replace(/"[^"]*"\s*$/, "") + `"${unit}"`;
|
||||
let plain = base;
|
||||
for (const p of UNIT_PREFIX) if (plain.startsWith(`"${p}"`)) plain = plain.slice(p.length + 2);
|
||||
return `"${unit}"` + plain;
|
||||
}
|
||||
|
||||
/** 활성 칸에 지금 걸린 서식 — 칸 자기 것 없으면 열 · 행 · 시트 기본(`Sheet.기본.서식`) · 그다음 0. */
|
||||
@@ -399,10 +405,13 @@ export function mountToolbar(ctx: SpreadsheetContext): PartHandle {
|
||||
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;
|
||||
const front = UNIT_PREFIX.includes(unit);
|
||||
const s = front ? 0 : (active.selectionStart ?? active.value.length);
|
||||
const e = front ? 0 : (active.selectionEnd ?? active.value.length);
|
||||
const was = active.selectionStart ?? active.value.length;
|
||||
active.value = active.value.slice(0, s) + unit + active.value.slice(e);
|
||||
active.setSelectionRange(s + unit.length, s + unit.length);
|
||||
const at = front ? was + unit.length : s + unit.length;
|
||||
active.setSelectionRange(at, at);
|
||||
active.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
}
|
||||
} else if (unitAsFormat.checked) {
|
||||
@@ -412,7 +421,12 @@ export function mountToolbar(ctx: SpreadsheetContext): PartHandle {
|
||||
const { r, c } = ctx.selection.활성;
|
||||
const sheet = ctx.sheet();
|
||||
const key = toA1(r, c);
|
||||
const text = cellToText(sheet.칸[key]) + unit;
|
||||
const now = cellToText(sheet.칸[key]);
|
||||
const text = UNIT_PREFIX.includes(unit)
|
||||
? now.startsWith(unit)
|
||||
? now
|
||||
: unit + now // 앞 단위 — 「300」 → 「Φ300」
|
||||
: now + unit;
|
||||
ctx.dispatch({ 종류: "칸", 시트: sheet.id, 칸: { [key]: textToCell(text, sheet.칸[key]) } });
|
||||
}
|
||||
renderRecent(pushRecentUnit(unit));
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""스프레드시트 단위 Φ(PLAN 81-5 · `spreadsheet_toolbar.ts`) — 길이 묶음 끝에 Φ · 숫자 앞에 붙임.
|
||||
|
||||
형식 코드 갈래(`attachUnitToFormat`)는 Node 로 함수를 떼어 돌리고, 칸 글 · 편집기 갈래는 글로 확인
|
||||
(화면 조작은 ORCA).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
TOOLBAR = ROOT / "A00_Common" / "spreadsheet" / "spreadsheet_toolbar.ts"
|
||||
|
||||
|
||||
def _text() -> str:
|
||||
return TOOLBAR.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
def test_형식_코드_앞_단위_뒤_단위():
|
||||
src = _text()
|
||||
prefix = re.search(r"^const UNIT_PREFIX = .*;$", src, re.M)
|
||||
func = re.search(r"^function attachUnitToFormat[\s\S]*?^}", src, re.M)
|
||||
assert prefix and func
|
||||
script = (
|
||||
prefix.group(0)
|
||||
+ "\n"
|
||||
+ func.group(0).replace(": string", "") # 형 이름 뗌 — Node 가 그대로 돌게
|
||||
+ """
|
||||
const f = attachUnitToFormat;
|
||||
console.log(JSON.stringify([
|
||||
f("", "Φ"), // 처음 = 「Φ」 + 0
|
||||
f("0.00", "Φ"), // 코드 앞
|
||||
f('"Φ"0', "Φ"), // 두 번 눌러도 한 번
|
||||
f('0"m"', "Φ"), // 뒤 단위는 그대로
|
||||
f('"Φ"0', "m"), // 뒤 단위를 붙여도 앞 단위 살아 있음
|
||||
f('0"m"', "㎜"), // 옛 동작 — 뒤 단위 바꿈
|
||||
f("", "m"),
|
||||
]));
|
||||
"""
|
||||
)
|
||||
out = subprocess.run(
|
||||
["node", "-e", script], capture_output=True, text=True, encoding="utf-8", check=True
|
||||
)
|
||||
assert json.loads(out.stdout) == [
|
||||
'"Φ"0',
|
||||
'"Φ"0.00',
|
||||
'"Φ"0',
|
||||
'"Φ"0"m"',
|
||||
'"Φ"0"m"',
|
||||
'0"㎜"',
|
||||
'0"m"',
|
||||
]
|
||||
|
||||
|
||||
def test_Φ_길이_묶음_끝_칸_글_편집기():
|
||||
src = _text()
|
||||
assert '["UnitGroup_area", ["㎡", "㎥", "m", "㎝", "㎜", "㎞", "Φ"]]' in src
|
||||
body = src.split("function insertUnit")[1].split("function renderRecent")[0]
|
||||
assert "UNIT_PREFIX.includes(unit)" in body
|
||||
assert (
|
||||
"now.startsWith(unit)" in body and "unit + now" in body
|
||||
) # 「300」 → 「Φ300」 · 두 번 안 붙음
|
||||
assert "const s = front ? 0" in body # 편집 중에도 글 맨 앞
|
||||
Reference in New Issue
Block a user