Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
This commit is contained in:
@@ -96,7 +96,12 @@ export const buildBill: TabBuild = (ctx): BillTable => {
|
||||
const title = r.kind === "title" ? "is-title" : r.kind === "group" ? "is-group" : "";
|
||||
return [title, r.auto ? "" : "is-hand"].filter((c) => c).join(" ") || null;
|
||||
},
|
||||
level: { key: "name", of: (i) => rows[i]?.level as number | undefined },
|
||||
// 228-1 레벨 = 셈 나무 깊이(들여 · 내어쓰기 · 끌기 = 다시 셈 · 다시 그림) · 글꼴 = 레벨 · 묶음(빈 공종도)
|
||||
level: {
|
||||
key: "name",
|
||||
of: (i) => rows[i]?.level as number | undefined,
|
||||
group: (i) => rows[i]?.kind !== "item",
|
||||
},
|
||||
menu: (i) => billMenu(ctx, raw, i),
|
||||
drop: (i, parent, before) => dropMove(ctx, raw, i, parent, before),
|
||||
onCell: (i, key) => {
|
||||
|
||||
@@ -71,6 +71,6 @@ def test_CSS_옛_레벨_규칙_없음_공용_표로():
|
||||
assert "is-indent" not in css and "is-flat" not in css and "is-lv" not in css
|
||||
common = (ROOT / "ui_template" / "ui_template_data_table.css").read_text(encoding="utf-8")
|
||||
assert re.search(
|
||||
r"td\.ui-data-table__lv \{\s*padding-left: calc\(var\(--spacing-8\) \+ var\(--dt-lv, 0\) \* 1em\);",
|
||||
r"td\.ui-data-table__lv \{\s*padding-left: calc\(var\(--spacing-8\) \+ var\(--dt-lv, 0\) \* var\(--dt-indent\)\);",
|
||||
common,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""200-2 B09 설계내역서 레벨 글 · 레벨 도구 줄 — 글 칸 왼쪽 맞춤 · 레벨 = 공종 · 명칭 칸 padding 들여쓰기 ·
|
||||
글자 크기 한 벌 · 단추 넷(고른 줄 · 못 하면 disabled) · 키 셋(Tab · Shift+Tab · Alt+↑↓) · 편집 중엔 키 안 먹음.
|
||||
글꼴 = 공용 표 레벨 글꼴(228-1 · 레벨 · 묶음 따라) · 단추 넷(고른 줄 · 못 하면 disabled) ·
|
||||
키 셋(Tab · Shift+Tab · Alt+↑↓) · 편집 중엔 키 안 먹음.
|
||||
|
||||
- 단추 · 키 = 우클릭 메뉴(billMenu) 같은 항목을 부름 — 줄 꼴 · 저장 꼴은 184-3 시험 그대로
|
||||
- 표 · 문서 = 가짜 DOM(줄 tr · 글 칸 input) · 다시 그림 = buildBill + levelBar 다시
|
||||
@@ -173,13 +174,16 @@ def test_키_셋_편집_중엔_안_먹음():
|
||||
assert out["keyAct"] == [IN, OUT, UP, DOWN, None, UP, None]
|
||||
|
||||
|
||||
def test_왼쪽_맞춤_레벨_들여쓰기_글자_크기_한_벌():
|
||||
def test_왼쪽_맞춤_레벨_들여쓰기_글꼴_공용_표():
|
||||
css = (B09 / "B09_Estimation_UI_Page.css").read_text("utf-8")
|
||||
bill = '.b09-estimation [data-b09-table="bill"]'
|
||||
# 201-1 레벨 들여쓰기 = 공용 표 level 옵션(test_201_1) · 레벨별 글자 크기 규칙 없음(한 벌)
|
||||
# 레벨 들여쓰기 · 글꼴 = 공용 표 level 옵션(test_201_1 · test_228_1) · 페이지 CSS 에 따로 두지 않음
|
||||
assert "is-lv" not in css and "font-size: 13px" not in css
|
||||
common = (ROOT / "ui_template" / "ui_template_data_table.css").read_text("utf-8")
|
||||
assert "td.ui-data-table__lv.is-lv1.is-node" in common
|
||||
src = (B09 / "B09_Estimation_UI_Tabs_Bill.ts").read_text("utf-8")
|
||||
assert 'level: { key: "name", of: (i) => rows[i]?.level' in src
|
||||
assert "of: (i) => rows[i]?.level as number | undefined," in src
|
||||
assert 'group: (i) => rows[i]?.kind !== "item",' in src
|
||||
# 글 칸 왼쪽 맞춤(수량만 오른쪽)
|
||||
assert re.search(
|
||||
re.escape(f'{bill} .ui-data-table__input:not([data-k="qty"]) {{') + r"\s*text-align: left;",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""201-1 공용 표 level(보기 전용) · mergeSame(같은 값 세로 병합) 옵션 + B08 · B09 적용 표.
|
||||
|
||||
- level { key, of } = 그 열 칸 class ui-data-table__lv · 줄 style --dt-lv(레벨 − 1) · 글 상자 칸도 · 다루기 단추 없음
|
||||
· 레벨 글꼴 class is-lv1~5 · 묶음 is-node(228-1 · test_228_1)
|
||||
- mergeSame [열] = 위아래 같은 글이 이어지면 rowspan · 앞 병합 열이 갈리면 갈림 · 빈 값 · 빈칸 글 · 고칠 칸 안 합침 ·
|
||||
합계 줄 안 합침 · 그린 구간(win) 안에서만 · 병합 표 칸 = data-c(열 번호)
|
||||
- 옵션 없으면 무변(rowspan · data-c · --dt-lv 없음)
|
||||
@@ -93,12 +94,13 @@ def test_mergeSame_같은_값만_rowspan():
|
||||
def test_level_보기_전용_들여쓰기():
|
||||
rows = _run()["level"]
|
||||
assert [re.search(r'style="--dt-lv:(\d)"', r).group(1) for r in rows] == ["0", "1", "0"]
|
||||
assert '<td class="ui-data-table__lv">a</td>' in rows[0]
|
||||
# 아래 줄이 더 깊음 = 묶음
|
||||
assert '<td class="ui-data-table__lv is-lv1 is-node">a</td>' in rows[0]
|
||||
assert rows[0].count("ui-data-table__lv") == 1 # 지정 열만
|
||||
assert '<td class="is-edit ui-data-table__lv" data-num="">' in rows[1] # 글 상자 칸도
|
||||
assert '<td class="is-edit ui-data-table__lv is-lv2" data-num="">' in rows[1] # 글 상자 칸도
|
||||
assert "button" not in "".join(rows) # 다루기 단추 없음(설계내역서만)
|
||||
css = (ROOT / "ui_template" / "ui_template_data_table.css").read_text("utf-8")
|
||||
pad = r"padding-left: calc\(var\(--spacing-8\) \+ var\(--dt-lv, 0\) \* 1em\);"
|
||||
pad = r"padding-left: calc\(var\(--spacing-8\) \+ var\(--dt-lv, 0\) \* var\(--dt-indent\)\);"
|
||||
assert re.search(r"td\.ui-data-table__lv \{\s*" + pad + r"\s*text-align: left;", css)
|
||||
assert re.search(r"td\.ui-data-table__lv \.ui-data-table__input \{\s*" + pad, css)
|
||||
assert re.search(r"td\.is-merged \{\s*vertical-align: top;", css)
|
||||
@@ -117,7 +119,7 @@ APPLIED = [
|
||||
("B09_Estimation/B09_Estimation_UI_Tabs_Sangeun.ts", 'mergeSame: ["kind"]'),
|
||||
("B09_Estimation/B09_Estimation_UI_Tabs_Total.ts", 'mergeSame: ["group", "part"]'),
|
||||
("B09_Estimation/B09_Estimation_UI_Tabs_Total.ts", 'mergeSame: ["part"]'),
|
||||
("B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts", 'level: { key: "name"'),
|
||||
("B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts", 'group: (i) => rows[i]?.kind !== "item"'),
|
||||
("B09_Estimation/B09_Estimation_UI_Shell.ts", "mergeSame: spec.mergeSame"),
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
"""228-1 표 레벨 = 들여쓰기에 따라 레벨 · 글꼴(크기 · 굵기) 자동 — 공용 표 level 옵션 · 설계내역서 · 보기 전용 표.
|
||||
|
||||
- 공용 표(`ui_template_data_table.ts`) level 칸 class = is-lv1~5(6 이상 = 5) · 묶음 줄 = is-node
|
||||
(group 이 주면 그 값 · 안 주면 바로 아래 줄 레벨이 더 큰 줄 · 그린 구간 밖 줄도 봄)
|
||||
- CSS = 묶음 굵게 · 크기 Lv1 16 · Lv2 15 · Lv3 14(기본) · Lv4 13 · Lv5 12 · 항목 줄은 14 보다 크지 않게 ·
|
||||
들여쓰기 한 단 = 표 기본 1em(레벨 글자 크기와 무관)
|
||||
- 설계내역서 = 들여 · 내어쓰기(메뉴 · 단추 · 키 · 끌기 같은 함수) → 셈 다시 → 그 줄과 자손 레벨 · 글꼴 바로 바뀜
|
||||
- 보기 전용 2단 표(blockLevel) · 총괄 · 원가계산서 = 같은 규칙(머리 줄 16 굵게 · 내용 14 보통)
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
from test_182_b09_bill_level import BOOKS, INPUTS, USER # noqa: E402
|
||||
from test_184_3_b09_bill_menu import _node, _ts # noqa: E402
|
||||
from test_185_1_b09_total_level import SUMS # noqa: E402
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B09_Estimation.B09_Estimation_Calc_Bill import compute_estimation # noqa: E402
|
||||
|
||||
IMPORTS = re.compile(r"^import[^;]*;\r?\n", re.MULTILINE)
|
||||
TABLE = IMPORTS.sub("", (ROOT / "ui_template" / "ui_template_data_table.ts").read_text("utf-8"))
|
||||
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill", "UI_Tabs_Bill_Drag",
|
||||
"UI_Tabs_Bill_Level"] # fmt: skip
|
||||
IN, OUT = "들여쓰기 →", "내어쓰기 ←"
|
||||
|
||||
#: 줄마다 [레벨 칸 class(레벨 · 묶음만), --dt-lv] — 레벨 열 칸만
|
||||
CLS = """
|
||||
const cls = (html) => html.split("<tbody>")[1].split("</tbody>")[0].split("</tr>").filter(Boolean)
|
||||
.map((r) => [(r.match(/class="([^"]*ui-data-table__lv[^"]*)"/) || [])[1]?.split(" ")
|
||||
.filter((c) => /^(ui-data-table__lv|is-lv\\d|is-node)$/.test(c)).join(" ") ?? null,
|
||||
Number((r.match(/--dt-lv:(\\d+)/) || [])[1])]);
|
||||
"""
|
||||
|
||||
|
||||
def _cls(body: str, names=SRC) -> dict:
|
||||
return json.loads(_node(_ts(names) + TABLE + CLS + body))
|
||||
|
||||
|
||||
# --- 공용 표 ------------------------------------------------------------------------------
|
||||
|
||||
TABLE_JS = """
|
||||
const columns = [{ key: "n", label: "명칭" }, { key: "v", label: "값", kind: "number" }];
|
||||
const lv = [1, 2, 3, 4, 5, 6, 2, null];
|
||||
const rows = lv.map((_, i) => ({ n: `r${i}`, v: i }));
|
||||
const of = (i) => lv[i];
|
||||
console.log(JSON.stringify({
|
||||
auto: cls(renderDataTableHtml({ columns, rows, level: { key: "n", of } })),
|
||||
given: cls(renderDataTableHtml({ columns, rows, level: { key: "n", of, group: (i) => i === 7 } })),
|
||||
win: cls(renderDataTableHtml({ columns, rows, level: { key: "n", of } }, { from: 2, to: 4, rowH: 28 })),
|
||||
edit: cls(renderDataTableHtml({ columns, rows, level: { key: "n", of }, editable: (i, k) => k === "n" })),
|
||||
}));
|
||||
"""
|
||||
|
||||
|
||||
def test_공용_표_레벨_글꼴_class():
|
||||
out = _cls(TABLE_JS, [])
|
||||
assert out["auto"] == [
|
||||
["ui-data-table__lv is-lv1 is-node", 0],
|
||||
["ui-data-table__lv is-lv2 is-node", 1],
|
||||
["ui-data-table__lv is-lv3 is-node", 2],
|
||||
["ui-data-table__lv is-lv4 is-node", 3],
|
||||
["ui-data-table__lv is-lv5 is-node", 4],
|
||||
["ui-data-table__lv is-lv5", 5], # 6 이상 = 5 글꼴 · 들여쓰기는 그대로
|
||||
["ui-data-table__lv is-lv2", 1], # 아래 줄이 얕음 = 항목
|
||||
["ui-data-table__lv is-lv1", 0], # 끝 줄 · 빈 레벨 = 1
|
||||
]
|
||||
assert [c for c, _ in out["given"]][6:] == [
|
||||
"ui-data-table__lv is-lv2",
|
||||
"ui-data-table__lv is-lv1 is-node",
|
||||
]
|
||||
assert "is-node" not in "".join(c for c, _ in out["given"][:6])
|
||||
# 그린 구간(2~3줄)의 끝 줄도 구간 밖 아래 줄로 묶음을 셈
|
||||
assert [c for c, _ in out["win"] if c] == [
|
||||
"ui-data-table__lv is-lv3 is-node",
|
||||
"ui-data-table__lv is-lv4 is-node",
|
||||
]
|
||||
assert out["edit"] == out["auto"] # 글 상자 칸도(글 상자 = font: inherit)
|
||||
|
||||
|
||||
def test_CSS_레벨_글꼴_규칙():
|
||||
css = (ROOT / "ui_template" / "ui_template_data_table.css").read_text("utf-8")
|
||||
tokens = dict(re.findall(r"(--dt-(?:lv\d-size|indent)): ([^;]+);", css))
|
||||
assert tokens == {
|
||||
"--dt-indent": "var(--text-body-sm)",
|
||||
"--dt-lv1-size": "var(--text-body)",
|
||||
"--dt-lv2-size": "15px",
|
||||
"--dt-lv4-size": "13px",
|
||||
"--dt-lv5-size": "var(--text-caption)",
|
||||
}
|
||||
theme = (ROOT / "ui_template" / "ui_template_theme.css").read_text("utf-8")
|
||||
assert "--text-body: 16px;" in theme and "--text-body-sm: 14px;" in theme
|
||||
assert "--text-caption: 12px;" in theme
|
||||
|
||||
def rule(sel: str) -> str:
|
||||
m = re.search(
|
||||
re.escape(f".ui-data-table__table td.ui-data-table__lv{sel} {{") + r"([^}]*)\}", css
|
||||
)
|
||||
return m.group(1).strip() if m else ""
|
||||
|
||||
assert rule(".is-node") == "font-weight: var(--font-weight-bold);"
|
||||
assert rule(".is-lv1.is-node") == "font-size: var(--dt-lv1-size);"
|
||||
assert rule(".is-lv2.is-node") == "font-size: var(--dt-lv2-size);"
|
||||
assert rule(".is-lv4") == "font-size: var(--dt-lv4-size);"
|
||||
assert rule(".is-lv5") == "font-size: var(--dt-lv5-size);"
|
||||
# 항목 Lv1 · 2 · Lv3 = 표 기본 14px(크기 규칙 없음) · 굵게 = 묶음만
|
||||
assert not rule(".is-lv1") and not rule(".is-lv2") and not rule(".is-lv3")
|
||||
assert css.count("var(--dt-lv, 0) * var(--dt-indent)") == 2 and "* 1em" not in css
|
||||
# 줄 높이 28px 안 — 가장 큰 글자(16px × 줄 간격 1.3)
|
||||
assert "--dt-row-h: 28px;" in css and 16 * 1.3 < 28
|
||||
|
||||
|
||||
# --- 설계내역서: 들여 · 내어쓰기 → 레벨 · 글꼴 바로 ------------------------------------------
|
||||
|
||||
BILL_JS = """
|
||||
let user = __USER__;
|
||||
const sheets = { bill: { rows: __ROWS__, totals: null } };
|
||||
const ctx = { sheets, payload: {}, get user() { return user; }, setUser: (u) => (user = u) };
|
||||
const spec = buildBill(ctx);
|
||||
const html = cls(renderDataTableHtml({ columns: spec.columns, rows: spec.rows, level: spec.level,
|
||||
editable: spec.editable }));
|
||||
const out = { view: Object.fromEntries(spec.rows.map((r, i) => [r.key, html[i]])) };
|
||||
const runs = __RUNS__;
|
||||
if (runs.length) {
|
||||
const [key, label] = runs[0];
|
||||
const item = billMenu(ctx, spec.rows, spec.rows.findIndex((r) => r.key === key))
|
||||
.find((x) => Array.isArray(x) && x[0] === label);
|
||||
item[1]();
|
||||
out.bill = user.bill;
|
||||
}
|
||||
console.log(JSON.stringify(out));
|
||||
"""
|
||||
|
||||
|
||||
def _bill(user, run=None):
|
||||
rows = compute_estimation(INPUTS, user, None, BOOKS)["bill"]["rows"]
|
||||
body = (
|
||||
BILL_JS.replace("__USER__", json.dumps(user, ensure_ascii=False))
|
||||
.replace("__ROWS__", json.dumps(rows, ensure_ascii=False))
|
||||
.replace("__RUNS__", json.dumps([run] if run else [], ensure_ascii=False))
|
||||
)
|
||||
return _cls(body)
|
||||
|
||||
|
||||
def _after(run):
|
||||
"""메뉴 한 번(들여 · 내어쓰기) → 저장 꼴 → 셈 다시(화면 setUser 와 같은 길) → 다시 그림."""
|
||||
bill = _bill(USER, run)["bill"]
|
||||
return _bill({**USER, "bill": bill})["view"]
|
||||
|
||||
|
||||
def test_내어쓰기_그_줄과_자손_레벨_글꼴_바로():
|
||||
before = _bill(USER)["view"]
|
||||
# 손 묶음 g2(Lv4 · 자식 b · c Lv5)
|
||||
assert before["hand:g2"] == ["ui-data-table__lv is-lv4 is-node", 3]
|
||||
assert before["hand:b"] == ["ui-data-table__lv is-lv5", 4]
|
||||
assert before["hand:c"] == ["ui-data-table__lv is-lv5", 4]
|
||||
assert before["hand:g1"] == ["ui-data-table__lv is-lv3 is-node", 2]
|
||||
after = _after(["hand:g2", OUT])
|
||||
assert after["hand:g2"] == ["ui-data-table__lv is-lv3 is-node", 2] # 14 굵게
|
||||
assert after["hand:b"] == ["ui-data-table__lv is-lv4", 3] # 자손도 한 단 위 · 13
|
||||
assert after["hand:c"] == ["ui-data-table__lv is-lv4", 3]
|
||||
assert after["hand:g1"] == before["hand:g1"] # 손 묶음 = 자식 없어도 묶음(group)
|
||||
|
||||
|
||||
def test_들여쓰기_항목이_묶음이_되면_굵게():
|
||||
before = _bill(USER)["view"]
|
||||
assert before["earth:5"] == ["ui-data-table__lv is-lv3", 2]
|
||||
assert before["hand:x"] == ["ui-data-table__lv is-lv3", 2]
|
||||
after = _after(["earth:5", IN]) # 위 형제 hand:x 아래로
|
||||
assert after["earth:5"] == ["ui-data-table__lv is-lv4", 3]
|
||||
assert after["hand:x"] == ["ui-data-table__lv is-lv3 is-node", 2] # 자식 생긴 항목 = 묶음 굵게
|
||||
# 제목 줄 = 묶음 · Lv1 16 · Lv2 15
|
||||
assert before["sec:1"] == ["ui-data-table__lv is-lv1 is-node", 0]
|
||||
assert before["sec:1.2"] == ["ui-data-table__lv is-lv2 is-node", 1]
|
||||
|
||||
|
||||
# --- 보기 전용 표: 같은 규칙 --------------------------------------------------------------
|
||||
|
||||
BLOCK_JS = """
|
||||
const columns = [{ key: "name", label: "명칭" }];
|
||||
const rows = ["머리", "줄1", "줄2", "합계", "머리2", "줄3"].map((name) => ({ name }));
|
||||
const top = (i) => [0, 3, 4].includes(i);
|
||||
console.log(JSON.stringify(cls(renderDataTableHtml({ columns, rows, level: blockLevel("name", top) }))));
|
||||
"""
|
||||
|
||||
TOTAL_JS = f"""
|
||||
const user = {{ estimate: {{}} }};
|
||||
const ctx = {{ sheets: {{ total: computeTotal({json.dumps(SUMS)}, user.estimate) }}, payload: {{}}, user,
|
||||
setUser: () => {{}} }};
|
||||
const view = (t) => {{
|
||||
const html = cls(renderDataTableHtml({{ columns: t.columns, rows: t.rows, level: t.level }}));
|
||||
return t.rows.map((r, i) => [r.key, r.level, html[i][0]]);
|
||||
}};
|
||||
console.log(JSON.stringify({{ total: view(buildTotal(ctx)), cost: view(buildCost(ctx)) }}));
|
||||
"""
|
||||
|
||||
|
||||
def test_보기_전용_2단_표_머리_16_굵게_내용_14_보통():
|
||||
out = _cls(BLOCK_JS)
|
||||
assert [c for c, _ in out] == [
|
||||
"ui-data-table__lv is-lv1 is-node",
|
||||
"ui-data-table__lv is-lv2",
|
||||
"ui-data-table__lv is-lv2",
|
||||
"ui-data-table__lv is-lv1", # 합계(아래 줄 = 다음 머리) = 14 · 굵게는 줄 class is-title
|
||||
"ui-data-table__lv is-lv1 is-node",
|
||||
"ui-data-table__lv is-lv2",
|
||||
]
|
||||
|
||||
|
||||
def test_총괄_원가계산서_같은_규칙():
|
||||
out = _cls(TOTAL_JS, ["Calc_Base", "Calc_Total", "UI_Tabs", "UI_Tabs_Total"])
|
||||
for tab in ("total", "cost"):
|
||||
rows = out[tab]
|
||||
for i, (key, lv, c) in enumerate(rows):
|
||||
node = i + 1 < len(rows) and (rows[i + 1][1] or 1) > (lv or 1)
|
||||
assert c == f"ui-data-table__lv is-lv{min(5, lv or 1)}" + (
|
||||
" is-node" if node else ""
|
||||
), (tab, key)
|
||||
cost = {k: c for k, _, c in out["cost"]}
|
||||
assert (
|
||||
"is-lv3" in cost["direct_material"] and "is-node" not in cost["direct_material"]
|
||||
) # 세목 14 보통
|
||||
assert cost["material"].endswith("is-lv2 is-node") # 비목 소계(아래 세목) 15 굵게
|
||||
@@ -10,6 +10,13 @@
|
||||
--dt-head: color-mix(in srgb, var(--color-accent) 6%, var(--color-surface));
|
||||
--dt-line: var(--color-border);
|
||||
--dt-edge: color-mix(in srgb, var(--color-text-secondary) 45%, var(--color-border));
|
||||
/* 들여쓰기 한 단 = 표 기본 글자 1em(레벨 글자 크기와 무관 · 끌기 한 단과 같음) */
|
||||
--dt-indent: var(--text-body-sm);
|
||||
/* 레벨 글자 크기(PLAN 228-1) — 줄 높이 28px 안 · Lv3 = 표 기본(14px) */
|
||||
--dt-lv1-size: var(--text-body);
|
||||
--dt-lv2-size: 15px;
|
||||
--dt-lv4-size: 13px;
|
||||
--dt-lv5-size: var(--text-caption);
|
||||
width: fit-content; /* 표가 좁으면 틀도 표만큼 */
|
||||
max-width: 100%;
|
||||
max-height: calc(100vh - 260px);
|
||||
@@ -173,9 +180,9 @@
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* 레벨(PLAN 201-1 · level 옵션 · 보기 전용) — 레벨 열 왼쪽 맞춤 · 레벨마다 1em 들여쓰기(글 상자도) */
|
||||
/* 레벨(PLAN 201-1 · level 옵션 · 보기 전용) — 레벨 열 왼쪽 맞춤 · 레벨마다 1em(표 기본) 들여쓰기(글 상자도) */
|
||||
.ui-data-table__table td.ui-data-table__lv {
|
||||
padding-left: calc(var(--spacing-8) + var(--dt-lv, 0) * 1em);
|
||||
padding-left: calc(var(--spacing-8) + var(--dt-lv, 0) * var(--dt-indent));
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -184,10 +191,31 @@
|
||||
}
|
||||
|
||||
.ui-data-table__table td.ui-data-table__lv .ui-data-table__input {
|
||||
padding-left: calc(var(--spacing-8) + var(--dt-lv, 0) * 1em);
|
||||
padding-left: calc(var(--spacing-8) + var(--dt-lv, 0) * var(--dt-indent));
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 레벨 글꼴(PLAN 228-1) — 묶음 줄(is-node) 굵게 · 크기는 레벨 · 항목 줄은 14px 보다 크지 않게(Lv1 · 2 항목 = 기본) */
|
||||
.ui-data-table__table td.ui-data-table__lv.is-node {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.ui-data-table__table td.ui-data-table__lv.is-lv1.is-node {
|
||||
font-size: var(--dt-lv1-size);
|
||||
}
|
||||
|
||||
.ui-data-table__table td.ui-data-table__lv.is-lv2.is-node {
|
||||
font-size: var(--dt-lv2-size);
|
||||
}
|
||||
|
||||
.ui-data-table__table td.ui-data-table__lv.is-lv4 {
|
||||
font-size: var(--dt-lv4-size);
|
||||
}
|
||||
|
||||
.ui-data-table__table td.ui-data-table__lv.is-lv5 {
|
||||
font-size: var(--dt-lv5-size);
|
||||
}
|
||||
|
||||
/* 같은 값 세로 병합(PLAN 201-1 · mergeSame) — 한 칸 글은 위에 */
|
||||
.ui-data-table__table td.is-merged {
|
||||
vertical-align: top;
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
* 줄 꼴(PLAN 155-2) = rowClass(줄) → 줄 class(예 is-title 굵게) · 안 주면 글 그대로
|
||||
* 창 끝까지(PLAN 199) = fillViewport → 표 칸 최대 높이를 창 아래 끝까지(줄이 늘어도 페이지 스크롤 0)
|
||||
* 레벨(PLAN 201-1 · 보기 전용) = level { key, of(줄) } → 그 열 왼쪽 맞춤 + 레벨마다 1em 들여쓰기(글 상자도)
|
||||
* 글꼴(PLAN 228-1) = 칸 class is-lv1~5 · 묶음(group · 안 주면 바로 아래 줄이 더 깊은 줄) = is-node 굵게
|
||||
* 크기 Lv1 16 · Lv2 15 · Lv3 14 · Lv4 13 · Lv5 12(px) · 항목 줄은 14 보다 크지 않게
|
||||
* 같은 값 세로 병합(PLAN 201-1) = mergeSame [열] → 위아래로 같은 값이 이어지면 한 칸(rowSpan) · 본문만
|
||||
* 앞 병합 열이 갈리면 뒤 병합 열도 갈림 · 빈 값 · 고칠 칸은 안 합침 · 칸 → 열 = `cellColumn`(병합 표는 data-c)
|
||||
* ========================================================================== */
|
||||
@@ -68,8 +70,13 @@ export interface DataTableOptions {
|
||||
cellClass?: (index: number, key: string) => string | null | undefined;
|
||||
/** 참이면 표 칸 최대 높이 = 창 아래 끝까지(창 크기 바뀌면 다시 셈) — 안 주면 CSS 고정값 */
|
||||
fillViewport?: boolean;
|
||||
/** 레벨(보기 전용) — key 열을 레벨마다 1em 들여씀 · of = 줄 레벨(1 = 맨 위 · 빈 값 = 1) */
|
||||
level?: { key: string; of: (index: number) => number | null | undefined };
|
||||
/** 레벨(보기 전용) — key 열을 레벨마다 1em 들여씀 · of = 줄 레벨(1 = 맨 위 · 빈 값 = 1) ·
|
||||
* group = 묶음 줄인지(안 주면 바로 아래 줄 레벨이 더 큰 줄) — 레벨 · 묶음 따라 글꼴(228-1) */
|
||||
level?: {
|
||||
key: string;
|
||||
of: (index: number) => number | null | undefined;
|
||||
group?: (index: number) => boolean;
|
||||
};
|
||||
/** 같은 값 세로 병합할 열 키(표마다 지정) — 위아래로 같은 글이 이어지면 rowSpan 한 칸 */
|
||||
mergeSame?: string[];
|
||||
}
|
||||
@@ -160,7 +167,10 @@ function rowHtml(
|
||||
const key = cols[j].key;
|
||||
const span = spans?.[j] ?? 1;
|
||||
if (!span) continue; // 위 칸에 합쳐짐
|
||||
const lvc = lv?.key === key ? " ui-data-table__lv" : "";
|
||||
const lvc =
|
||||
lv?.key === key
|
||||
? ` ui-data-table__lv is-lv${Math.min(5, lv.n + 1)}${lv.node ? " is-node" : ""}`
|
||||
: "";
|
||||
const c = spans ? ` data-c="${j}"` : "";
|
||||
if (edit !== null && editable?.(edit, key)) {
|
||||
const v = r[key];
|
||||
@@ -199,9 +209,9 @@ function rowHtml(
|
||||
: `<tr>${tr}</tr>`;
|
||||
}
|
||||
|
||||
/** 줄 곁값 — lv = 레벨 열 · 들여쓸 칸 수 · spans = 열마다 rowSpan(0 = 위 칸에 합쳐짐) */
|
||||
/** 줄 곁값 — lv = 레벨 열 · 들여쓸 칸 수 · 묶음 줄인지 · spans = 열마다 rowSpan(0 = 위 칸에 합쳐짐) */
|
||||
interface RowMore {
|
||||
lv?: { key: string; n: number };
|
||||
lv?: { key: string; n: number; node: boolean };
|
||||
spans?: number[];
|
||||
}
|
||||
|
||||
@@ -276,6 +286,7 @@ export function renderDataTableHtml(o: DataTableOptions, win?: RowWindow): strin
|
||||
const spans = o.mergeSame?.length ? mergeSpans(o, from, to) : null;
|
||||
for (let i = from; i < to; i++) {
|
||||
const lvN = o.level ? Math.max(0, (o.level.of(i) ?? 1) - 1) : 0;
|
||||
const node = o.level ? isNode(o.level, i, n) : false;
|
||||
body += rowHtml(
|
||||
cols,
|
||||
fmts,
|
||||
@@ -290,7 +301,7 @@ export function renderDataTableHtml(o: DataTableOptions, win?: RowWindow): strin
|
||||
o.rowClass?.(i),
|
||||
o.cellClass ? (key) => o.cellClass?.(i, key) : undefined,
|
||||
{
|
||||
...(o.level ? { lv: { key: o.level.key, n: lvN } } : {}),
|
||||
...(o.level ? { lv: { key: o.level.key, n: lvN, node } } : {}),
|
||||
...(spans ? { spans: spans[i - from] } : {}),
|
||||
},
|
||||
);
|
||||
@@ -304,6 +315,12 @@ export function renderDataTableHtml(o: DataTableOptions, win?: RowWindow): strin
|
||||
);
|
||||
}
|
||||
|
||||
/** 묶음 줄(228-1) — group 이 주면 그 값 · 안 주면 바로 아래 줄 레벨이 더 큼 */
|
||||
function isNode(lv: NonNullable<DataTableOptions["level"]>, i: number, n: number): boolean {
|
||||
if (lv.group) return lv.group(i);
|
||||
return i + 1 < n && (lv.of(i + 1) ?? 1) > (lv.of(i) ?? 1);
|
||||
}
|
||||
|
||||
/** 화면 위아래로 더 그려 둘 줄 수 · 구간은 이 단위로 끊어 스크롤마다 다시 그리지 않음 */
|
||||
const OVER = 40;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user