feat(B09): 설계내역서 레벨 단추 보조 탭 줄로 이동 · 표 위 도구 줄 비움(229-1)
This commit is contained in:
@@ -81,18 +81,6 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 200-2 레벨 도구 줄 — 단추 넷 + 안내 한 줄 */
|
||||
.b09-estimation__level {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
}
|
||||
|
||||
.b09-estimation__level .b09-estimation__hint {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 181-2 표 보기 — 목록표에서 고른 호표 줄 구간 전체 붉은 외곽선(머리 accent 밑줄은 덮음) */
|
||||
.b09-estimation .ui-data-table__table tbody tr.is-pick td {
|
||||
--pick-t: inset 0 0 0 0 transparent;
|
||||
|
||||
@@ -260,11 +260,12 @@ export function renderB09Estimation(root: HTMLElement): void {
|
||||
}),
|
||||
);
|
||||
}
|
||||
// 200-2 설계내역서 = 표 위 레벨 도구 줄 · 고른 줄 강조 · 202-1 안내 줄 = 표 아래
|
||||
// 200-2 설계내역서 = 고른 줄 강조 · 229-1 레벨 단추 = 보조 탭 줄 오른쪽 · 202-1 안내 줄 = 표 아래
|
||||
const bill = (spec as BillTable).menu ? (spec as BillTable) : null;
|
||||
const table = makeTable(bill ? (i) => pickedClass(bill.rows[i]) : undefined);
|
||||
const level = bill ? levelBar(table.root, bill) : null;
|
||||
panel.replaceChildren(...(level ? [level] : []), table.root, ...(hint ? [hint] : []));
|
||||
if (level) bars.setTools(level.tools);
|
||||
panel.replaceChildren(table.root, ...(level ? [level.guide] : []), ...(hint ? [hint] : []));
|
||||
if (focus !== undefined) {
|
||||
const index = rowIndex(spec.rows, focus);
|
||||
if (index >= 0) requestAnimationFrame(() => table.focusRow(index));
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* =============================================================================
|
||||
* B09_Estimation_UI_Tabs_Bill_Level.ts
|
||||
* B09 설계내역서 레벨 도구 줄(PLAN 200-2) — 표 위 [→ 들여쓰기] [← 내어쓰기] [↑ 위로] [↓ 아래로] · 키 · 안내 한 줄.
|
||||
* B09 설계내역서 레벨 단추(PLAN 200-2 · 229-1) — 보조 탭 줄 오른쪽 [→ 들여쓰기] [← 내어쓰기] [↑ 위로] [↓ 아래로] ·
|
||||
* 키 · 안내 한 줄(표 아래).
|
||||
*
|
||||
* 단추 = 보조 탭 줄 도구 자리(`…_UI_TabBar` setTools) · 안내 글 = 표 아래(202-1)
|
||||
* 동작 = 줄 우클릭 메뉴(`…_UI_Tabs_Bill_Edit` billMenu)의 같은 항목을 부름 — 메뉴에 없으면 단추 흐림 · 키 안 먹음
|
||||
* 고른 줄 = 표 줄 누름(키로 기억 · 다시 그려도 남음) · 표시 = 줄 class is-focus(Shell 이 줄 class 곁값으로)
|
||||
* 키 = Tab 들여쓰기 · Shift+Tab 내어쓰기 · Alt+↑↓ 옮기기 · Esc 편집 끝(편집 아니면 고름 풂)
|
||||
@@ -38,7 +40,7 @@ let picked: string | null = null;
|
||||
/** 지금 그린 도구 줄 — 키가 부름 */
|
||||
let live: {
|
||||
root: HTMLElement;
|
||||
bar: HTMLElement;
|
||||
tools: HTMLElement[];
|
||||
run: (label: string) => void;
|
||||
sync: () => void;
|
||||
} | null = null;
|
||||
@@ -78,7 +80,13 @@ function listenKeys(): void {
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if (!live?.root.isConnected || picked === null) return;
|
||||
const at = document.activeElement;
|
||||
if (at && at !== document.body && !live.root.contains(at) && !live.bar.contains(at)) return;
|
||||
if (
|
||||
at &&
|
||||
at !== document.body &&
|
||||
!live.root.contains(at) &&
|
||||
!live.tools.includes(at as HTMLElement)
|
||||
)
|
||||
return;
|
||||
// Esc = 편집 끝(표에 초점 · 키 받음) · 편집 아닐 때 = 고름 풂
|
||||
if (event.key === "Escape") {
|
||||
if (!editing(at)) {
|
||||
@@ -95,8 +103,11 @@ function listenKeys(): void {
|
||||
});
|
||||
}
|
||||
|
||||
/** 표 위 레벨 도구 줄 — 표 root 에 줄 누름(고름)을 붙임 */
|
||||
export function levelBar(root: HTMLElement, table: BillTable): HTMLElement {
|
||||
/** 레벨 단추 넷(보조 탭 줄로) · 안내 글(표 아래로) — 표 root 에 줄 누름(고름)을 붙임 */
|
||||
export function levelBar(
|
||||
root: HTMLElement,
|
||||
table: BillTable,
|
||||
): { tools: HTMLElement[]; guide: HTMLElement } {
|
||||
const index = (): number =>
|
||||
picked === null ? -1 : table.rows.findIndex((r) => String(r.key) === picked);
|
||||
const run = (label: string): void => menuActs(table, index()).get(label)?.();
|
||||
@@ -110,13 +121,10 @@ export function levelBar(root: HTMLElement, table: BillTable): HTMLElement {
|
||||
button.dataset.b09Level = label;
|
||||
return button;
|
||||
});
|
||||
const guide = document.createElement("span");
|
||||
guide.className = "b09-estimation__hint";
|
||||
const guide = document.createElement("p");
|
||||
guide.className = "ui-general-block__empty";
|
||||
guide.textContent = GUIDE;
|
||||
attachHoverInfo(guide, GUIDE_MORE);
|
||||
const bar = document.createElement("div");
|
||||
bar.className = "b09-estimation__level";
|
||||
bar.append(...buttons, guide);
|
||||
const sync = (): void => {
|
||||
const acts = menuActs(table, index());
|
||||
ACTS.forEach(([, label], k) => (buttons[k].disabled = !acts.has(label)));
|
||||
@@ -132,7 +140,7 @@ export function levelBar(root: HTMLElement, table: BillTable): HTMLElement {
|
||||
});
|
||||
attachDrag(root, table, (key) => (picked = key));
|
||||
sync();
|
||||
live = { root, bar, run, sync };
|
||||
live = { root, tools: buttons, run, sync };
|
||||
listenKeys();
|
||||
return bar;
|
||||
return { tools: buttons, guide };
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ const draw = () => {
|
||||
const at = (key) => table.rows.findIndex((r) => r.key === key);
|
||||
const tr = (key) => root.kids[at(key)];
|
||||
const pick = (key) => root.fire("click", { target: tr(key).kids[0] });
|
||||
const buttons = () => bar.kids.filter((k) => k.tagName === "BUTTON");
|
||||
const buttons = () => bar.tools;
|
||||
const off = () => buttons().map((b) => b.disabled);
|
||||
const order = () => (user.bill?.rows ?? []).map((e) => [e.key, e.parent ?? null]);
|
||||
const key = (k, more = {}) => {
|
||||
@@ -79,7 +79,7 @@ const key = (k, more = {}) => {
|
||||
const out = {};
|
||||
draw();
|
||||
out.labels = buttons().map((b) => [b.label, b.dataset.b09Level, b.variant]);
|
||||
out.guide = bar.kids.at(-1).textContent;
|
||||
out.guide = bar.guide.textContent;
|
||||
out.hover = hovers.length;
|
||||
out.none = off();
|
||||
pick("earth:4");
|
||||
@@ -189,12 +189,14 @@ def test_왼쪽_맞춤_레벨_들여쓰기_글꼴_공용_표():
|
||||
re.escape(f'{bill} .ui-data-table__input:not([data-k="qty"]) {{') + r"\s*text-align: left;",
|
||||
css,
|
||||
)
|
||||
assert re.search(r"\.b09-estimation__level \{\s*display: flex;", css)
|
||||
assert ".b09-estimation__level" not in css # 229-1 표 위 도구 줄 없앰
|
||||
|
||||
|
||||
def test_셸_배선():
|
||||
src = (B09 / "B09_Estimation_UI_Shell.ts").read_text("utf-8")
|
||||
assert "makeTable(bill ? (i) => pickedClass(bill.rows[i]) : undefined)" in src
|
||||
assert "...(level ? [level] : []), table.root" in src
|
||||
assert "if (level) bars.setTools(level.tools);" in src # 229-1 단추 = 보조 탭 줄
|
||||
assert "panel.replaceChildren(table.root, ...(level ? [level.guide] : [])" in src
|
||||
lvl = (B09 / "B09_Estimation_UI_Tabs_Bill_Level.ts").read_text("utf-8")
|
||||
assert "table.menu(index)" in lvl # 단추 · 키 = 우클릭 메뉴 같은 함수
|
||||
assert "b09-estimation__level" not in lvl
|
||||
|
||||
@@ -105,9 +105,9 @@ def test_묶음_탭_단추가_보조_줄에_안내는_아래에():
|
||||
def test_잇기_쉘_안내_줄_아래_도구_비움():
|
||||
shell = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Shell.ts").read_text("utf-8")
|
||||
assert (
|
||||
"panel.replaceChildren(...(level ? [level] : []), table.root, ...(hint ? [hint] : []))"
|
||||
"panel.replaceChildren(table.root, ...(level ? [level.guide] : []), ...(hint ? [hint] : []))"
|
||||
in shell
|
||||
) # 200-2 레벨 줄 · 203-1 연결 점검 탭 없앰과 합침
|
||||
) # 229-1 레벨 단추 = 보조 탭 줄 · 안내 글 = 표 아래 · 안내 줄 = 맨 아래
|
||||
assert "...(hint ? [hint] : []), table.root" not in shell # 위 자리 없음
|
||||
assert re.search(r"bars\.draw\(tab\);\s*bars\.setTools\(\[\]\);", shell)
|
||||
assert "setTools: bars.setTools" in shell
|
||||
|
||||
@@ -57,7 +57,7 @@ out.altLeft = press("earth:4", "ArrowLeft", { altKey: true });
|
||||
user = {}; draw(); pick("earth:5");
|
||||
doc.activeElement = tr("earth:5").kids[0].kids[0];
|
||||
out.editing = [key("ArrowRight"), key("ArrowUp"), order()];
|
||||
out.guide = bar.kids.at(-1).textContent;
|
||||
out.guide = bar.guide.textContent;
|
||||
console.log(JSON.stringify(out));
|
||||
"""
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
"""229-1 B09 설계내역서 레벨 단추 = 보조 탭 줄 오른쪽 — 단추 넷 · 다른 탭엔 없음 · 표 위 도구 줄 없음.
|
||||
|
||||
- 단추 = 보조 탭 줄 도구 자리(`setTools`) · 안내 글 = 표 아래 · 못 하는 동작 = 흐림(200-2 그대로)
|
||||
- 쉘 배선 = 소스 글 대조(test_200_2 · test_202_1 과 같은 꼴)
|
||||
"""
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from resources.tester.helper_b09_group_cards import TABS, run # noqa: E402
|
||||
|
||||
B09 = "B09_Estimation/B09_Estimation_UI_"
|
||||
FILES = [TABS, f"{B09}TabBar.ts", f"{B09}Tabs_Bill_Level.ts"]
|
||||
# 가짜 DOM 에 줄 찾기(closest · querySelector(All))만 더함 · 끌기 = 빈 함수
|
||||
PRELUDE = r"""
|
||||
document.addEventListener = () => {};
|
||||
const attachDrag = () => {};
|
||||
E.prototype.closest = function () { for (let e = this; e; e = e.parent) if (e.tagName === "TR") return e; return null; };
|
||||
E.prototype.querySelectorAll = function () { return this.all("TR").filter((t) => t.cls.has("is-focus")); };
|
||||
E.prototype.querySelector = function (s) {
|
||||
const i = s.match(/data-i="(-?\d+)"/)?.[1];
|
||||
return this.children.find((c) => c.dataset.i === i) ?? null;
|
||||
};
|
||||
"""
|
||||
|
||||
|
||||
def test_설계내역서_보조_줄에_단추_넷_다른_탭엔_없음():
|
||||
out = run(
|
||||
FILES,
|
||||
"""
|
||||
const bars = createTabBars(() => {});
|
||||
const items = ["들여쓰기 →", "내어쓰기 ←", "위로 ↑", "아래로 ↓"].map((t) => [t, () => {}]);
|
||||
const table = { rows: [{ key: "r1" }], menu: (i) => (i === 0 ? items.slice(1, 3) : []) };
|
||||
const root = document.createElement("div");
|
||||
const tr = document.createElement("tr");
|
||||
tr.dataset.i = "0";
|
||||
root.append(tr);
|
||||
const toolsOf = () => bars.sub.children[bars.sub.children.length - 1];
|
||||
const snap = (lv) => ({
|
||||
labels: toolsOf().children.map((b) => b.textContent),
|
||||
levels: toolsOf().children.map((b) => b.dataset.b09Level ?? null),
|
||||
guideIn: lv ? lv.guide.textContent.length > 0 : null,
|
||||
});
|
||||
const show = (tab) => {
|
||||
bars.draw(tab);
|
||||
bars.setTools([]);
|
||||
if (tab !== "bill") return null;
|
||||
const lv = levelBar(root, table);
|
||||
bars.setTools(lv.tools);
|
||||
return lv;
|
||||
};
|
||||
const bill = show("bill");
|
||||
const a = snap(bill);
|
||||
const disabledBefore = bill.tools.map((b) => b.disabled);
|
||||
tr.click();
|
||||
const disabledAfter = bill.tools.map((b) => b.disabled);
|
||||
show("dg");
|
||||
const b = snap(null);
|
||||
show("bill");
|
||||
const c = snap(null);
|
||||
console.log(JSON.stringify({
|
||||
a, disabledBefore, disabledAfter, b: b.labels, c: c.labels,
|
||||
guideTag: bill.guide.tagName, guideCls: bill.guide.className, subBlank: bars.sub.classList.contains("b09-estimation__tabs--blank"),
|
||||
}));
|
||||
""",
|
||||
PRELUDE,
|
||||
)
|
||||
names = ["→ 들여쓰기", "← 내어쓰기", "↑ 위로", "↓ 아래로"]
|
||||
assert out["a"]["labels"] == names # 설계내역서 = 단추 넷(보조 줄 오른쪽 도구 자리)
|
||||
assert out["a"]["levels"] == ["들여쓰기 →", "내어쓰기 ←", "위로 ↑", "아래로 ↓"]
|
||||
assert out["a"]["guideIn"] is True # 안내 글 = 단추 줄이 아니라 따로(표 아래로)
|
||||
assert out["guideTag"] == "P" and "ui-general-block__empty" in out["guideCls"]
|
||||
assert out["subBlank"] # 하위 탭 없는 묶음 = 빈 보조 줄(숨은 단추 + 도구)
|
||||
assert out["disabledBefore"] == [True] * 4 # 고른 줄 없음 = 흐림
|
||||
assert out["disabledAfter"] == [True, False, False, True] # 줄 누름 = 메뉴 항목대로
|
||||
assert out["b"] == [] # 다른 탭(일위대가표) = 단추 없음
|
||||
assert out["c"] == names # 설계내역서로 돌아오면 다시 있음
|
||||
|
||||
|
||||
def test_표_위_도구_줄_없음_쉘_배선():
|
||||
shell = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Shell.ts").read_text("utf-8")
|
||||
assert "if (level) bars.setTools(level.tools);" in shell
|
||||
assert "panel.replaceChildren(table.root, ...(level ? [level.guide] : [])" in shell
|
||||
assert "...(level ? [level] : [])" not in shell # 표 위 자리 없음
|
||||
css = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Page.css").read_text("utf-8")
|
||||
lvl = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Tabs_Bill_Level.ts").read_text("utf-8")
|
||||
assert "b09-estimation__level" not in css and "b09-estimation__level" not in lvl
|
||||
# 키 · 단추 = 우클릭 메뉴 같은 항목 · 키 안 바뀜(Tab · Shift+Tab · Alt+↑↓ · 방향키)
|
||||
assert re.search(r"ACTS\[e\.shiftKey \? 1 : 0\]\[1\]", lvl)
|
||||
Reference in New Issue
Block a user