feat(B08·B09): 표 칸 안내 · 경고 줄 맨 아래 · 박스보기 · 가로 열 수 보조 탭 줄 오른쪽(202-1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jonfdxn1Xit9bdkNJpE4G
This commit is contained in:
@@ -152,13 +152,13 @@ export function note(text: string): HTMLElement {
|
||||
return p;
|
||||
}
|
||||
|
||||
/** 표 한 장 — 공용 컨테이너(카드) 안에 설명 · 공용 표 · 아래 줄 */
|
||||
/** 표 한 장 — 공용 컨테이너(카드) 안에 공용 표 · 아래 줄 · 맨 아래 안내 · 경고 줄(202-1) */
|
||||
export function tableCard(
|
||||
info: HTMLElement[],
|
||||
table: HTMLElement,
|
||||
after: HTMLElement[] = [],
|
||||
): HTMLElement {
|
||||
return createCard({ body: [...info, table, ...after] });
|
||||
return createCard({ body: [table, ...after, ...info] });
|
||||
}
|
||||
|
||||
function stationRow(row: EarthworkRow, first: boolean, interval: number): DataRow {
|
||||
|
||||
@@ -356,7 +356,7 @@ export async function renderMaterialSheet(
|
||||
const concreteNote = note(""); // 콘크리트 전용 빈 까닭(163-5) — 조작마다 다시
|
||||
concreteNote.dataset.b08MaterialConcreteWhy = "";
|
||||
if (sheet === "concrete") notes.append(concreteNote);
|
||||
toolbar.append(notes, save);
|
||||
toolbar.append(save);
|
||||
|
||||
let built = BUILD[sheet](sheets, overrides);
|
||||
const table = createDataTable({
|
||||
@@ -395,7 +395,7 @@ export async function renderMaterialSheet(
|
||||
concreteNote.hidden = !concreteNote.textContent;
|
||||
};
|
||||
redraw();
|
||||
host.replaceChildren(createCard({ body: [toolbar, table.root, excel] }));
|
||||
host.replaceChildren(createCard({ body: [toolbar, table.root, excel, notes] })); // 202-1 안내 · 경고 = 맨 아래
|
||||
onOptionsChange(host, () => {
|
||||
sheets = calc();
|
||||
redraw();
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
.b08-material__toolbar {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-end;
|
||||
gap: var(--spacing-16);
|
||||
margin-bottom: var(--spacing-8);
|
||||
}
|
||||
|
||||
@@ -281,8 +281,7 @@ export async function renderStructureSummary(host: HTMLElement, projectId: strin
|
||||
)
|
||||
: note(L("B08_Quantity_Summary_NoStation"));
|
||||
let table = draw();
|
||||
parts.push(table);
|
||||
host.replaceChildren(createCard({ body: parts })); // 공용 카드 안(토공 표와 같은 꼴 · 143-3)
|
||||
host.replaceChildren(createCard({ body: [table, ...parts] })); // 공용 카드 안(토공 표와 같은 꼴 · 143-3) · 안내 · 경고 = 표 아래(202-1)
|
||||
onOptionsChange(host, () => {
|
||||
const next = draw();
|
||||
table.replaceWith(next);
|
||||
|
||||
@@ -36,30 +36,30 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
/* 171-3 묶음 탭 머리 — 안내 글 + 보기 단추 */
|
||||
.b09-estimation__bar {
|
||||
/* 202-1 보조 탭 줄 오른쪽 도구(박스보기 · 가로 열 수) — 빈 보조 줄에서도 보임 · 단추 · 글 한 줄 */
|
||||
.b09-estimation__tools {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
flex: none;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: var(--spacing-8);
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* 185-2 보기 단추 글 한 줄 */
|
||||
.b09-estimation__bar .ui-btn {
|
||||
.b09-estimation__tools .ui-btn,
|
||||
.b09-estimation__tools .ui-group-cards__label {
|
||||
flex: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 200-1 묶음 탭 도구 줄 — 안내 글 = 남는 자리 한 줄 · 넘치면 줄임표(전문 = 공용 호버) */
|
||||
.b09-estimation__bar {
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
/* 202-1 안내 줄 = 표 · 카드 판 아래(묶음 탭은 한 줄 줄임표 · 전문 = 공용 호버) */
|
||||
.b09-estimation .ui-data-table + .ui-general-block__empty,
|
||||
.b09-estimation .ui-group-cards + .ui-general-block__empty {
|
||||
margin: var(--spacing-8) 0 0;
|
||||
}
|
||||
|
||||
.b09-estimation__line-hint {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -184,6 +184,7 @@ export function renderB09Estimation(root: HTMLElement): void {
|
||||
const show = (tab: TabId, focus?: string): void => {
|
||||
current = tab;
|
||||
bars.draw(tab);
|
||||
bars.setTools([]); // 보기 단추 · 열 수는 묶음 탭만 다시 채움(202-1)
|
||||
if (!projectId) return void panel.replaceChildren(note("프로젝트를 먼저 고르세요."));
|
||||
if (!payload) return void panel.replaceChildren(note("원가 자료를 불러오는 중…"));
|
||||
const build = BUILDERS[tab];
|
||||
@@ -246,11 +247,20 @@ export function renderB09Estimation(root: HTMLElement): void {
|
||||
if (isGroupTab(tab)) {
|
||||
const redraw = (): void => show(tab);
|
||||
return void panel.replaceChildren(
|
||||
...renderGroupPanel({ tab, spec, hint, focus, goto, makeTable, redraw }),
|
||||
...renderGroupPanel({
|
||||
tab,
|
||||
spec,
|
||||
hint,
|
||||
focus,
|
||||
goto,
|
||||
makeTable,
|
||||
redraw,
|
||||
setTools: bars.setTools,
|
||||
}),
|
||||
);
|
||||
}
|
||||
const table = makeTable();
|
||||
panel.replaceChildren(...(hint ? [hint] : []), table.root);
|
||||
panel.replaceChildren(table.root, ...(hint ? [hint] : [])); // 202-1 안내 줄 = 표 아래
|
||||
if (focus !== undefined) {
|
||||
const index = rowIndex(spec.rows, focus);
|
||||
if (index >= 0) requestAnimationFrame(() => table.focusRow(index));
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* 위 탭 = `TAB_GROUPS`(`…_UI_Tabs`) · 아래 탭 하나뿐인 위 탭 = 보이지 않는 sm 단추 한 줄(높이만 채움)
|
||||
* 위 탭 누름 = 그 묶음에서 마지막으로 본 아래 탭(이 화면 안 Map · 저장 안 함) · 없으면 첫 아래 탭
|
||||
* 탭 이동은 `draw(탭)` — 이동이 묶음을 넘어도 위 탭이 따라 켜짐
|
||||
* 보기 단추 · 가로 열 수(202-1) = 아래 줄 오른쪽 맞춤 자리(`setTools`) · 빈 줄에서도 보임
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton } from "@ui/ui_template_elements";
|
||||
@@ -15,6 +16,8 @@ export interface TabBars {
|
||||
sub: HTMLElement;
|
||||
/** 이 탭이 켜진 꼴로 두 줄을 다시 그림 */
|
||||
draw: (tab: TabId) => void;
|
||||
/** 아래 줄 오른쪽 도구(박스보기 · 가로 열 수) — 빈 목록 = 비움 */
|
||||
setTools: (tools: HTMLElement[]) => void;
|
||||
}
|
||||
|
||||
function tabButton(label: string, on: boolean, size: "sm" | undefined, pick: () => void) {
|
||||
@@ -35,6 +38,8 @@ export function createTabBars(onPick: (tab: TabId) => void): TabBars {
|
||||
top.setAttribute("role", "tablist");
|
||||
const sub = document.createElement("div");
|
||||
sub.className = "b09-estimation__tabs";
|
||||
const tools = document.createElement("div");
|
||||
tools.className = "b09-estimation__tools";
|
||||
const lastSeen = new Map<TabId, TabId>();
|
||||
|
||||
const draw = (tab: TabId): void => {
|
||||
@@ -53,11 +58,11 @@ export function createTabBars(onPick: (tab: TabId) => void): TabBars {
|
||||
const single = picked.length === 1;
|
||||
sub.classList.toggle("b09-estimation__tabs--blank", single);
|
||||
sub.setAttribute("role", single ? "presentation" : "tablist");
|
||||
sub.setAttribute("aria-hidden", String(single));
|
||||
if (single) {
|
||||
const ghost = createButton({ label: tabName(picked[0]), variant: "ghost", size: "sm" });
|
||||
ghost.tabIndex = -1;
|
||||
return void sub.replaceChildren(ghost);
|
||||
ghost.setAttribute("aria-hidden", "true"); // 도구는 보이는 줄이라 줄 전체를 숨기지 않음
|
||||
return void sub.replaceChildren(ghost, tools);
|
||||
}
|
||||
sub.replaceChildren(
|
||||
...picked.map((id) => {
|
||||
@@ -65,7 +70,9 @@ export function createTabBars(onPick: (tab: TabId) => void): TabBars {
|
||||
button.dataset.b09Tab = id;
|
||||
return button;
|
||||
}),
|
||||
tools,
|
||||
);
|
||||
};
|
||||
return { top, sub, draw };
|
||||
const setTools = (items: HTMLElement[]): void => tools.replaceChildren(...items);
|
||||
return { top, sub, draw, setTools };
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
* 머리 줄 = 묶음 첫 줄 · 합계 줄 = 키 끝 `|합계` · `|총계` · 그 밖 = 몸 줄(표 줄 번호 그대로)
|
||||
* 목록표에서 온 호표(181-2) = 박스 붉은 외곽선 · 표 보기 = 그 호표 줄 구간 전체 붉은 외곽선
|
||||
* 보기 · 가로 열 수 기억 = `b_page_state`(`b09-group-view` · `b09-group-cols` · 탭마다) · 못 읽으면 카드 · 2
|
||||
* 도구 줄(200-1) = 안내 글 + 가로 열 수 + 보기 단추 한 줄 · 안내 글 길면 줄임표 + 공용 호버로 전문
|
||||
* 보기 단추 · 가로 열 수(202-1) = 페이지 보조 탭 줄 오른쪽(`setTools`) · 안내 글 = 표 · 카드 판 맨 아래
|
||||
* 안내 글 길면 한 줄 줄임표 + 공용 호버로 전문(200-1)
|
||||
* ========================================================================== */
|
||||
|
||||
import type { DataRow, DataTable } from "@ui/ui_template_data_table";
|
||||
@@ -176,7 +177,7 @@ function numberFormat(spec: TabTable, key: string): (v: number | null | undefine
|
||||
|
||||
/* ── 그리기 ───────────────────────────────────────────────────────────── */
|
||||
|
||||
/** 안내 글 = 도구 줄 한 칸(줄임표) · 잘렸을 때만 마우스 올리면 전문(공용 호버 185-3) */
|
||||
/** 안내 글 = 한 줄(줄임표) · 잘렸을 때만 마우스 올리면 전문(공용 호버 185-3) */
|
||||
function lineHint(hint: HTMLElement | null): HTMLElement | null {
|
||||
if (!hint) return null;
|
||||
hint.classList.add("b09-estimation__line-hint");
|
||||
@@ -187,7 +188,7 @@ function lineHint(hint: HTMLElement | null): HTMLElement | null {
|
||||
/** 이 화면 안 보기(기억이 안 돼도 바꾼 보기 유지) */
|
||||
const chosen = new Map<TabId, GroupView>();
|
||||
|
||||
/** 묶음 탭 — 카드 = 카드 판(안내 · 가로 열 수 · 보기 단추 한 줄) · 표 = 머리(안내 + 보기 단추 한 줄) + 표 */
|
||||
/** 묶음 탭 — 카드 = 카드 판 + 안내 글 · 표 = 표 + 안내 글 · 보기 단추 · 열 수 = `setTools`(보조 탭 줄 오른쪽) */
|
||||
export function renderGroupPanel(o: {
|
||||
tab: TabId;
|
||||
spec: TabTable;
|
||||
@@ -196,6 +197,7 @@ export function renderGroupPanel(o: {
|
||||
goto: (link: Link | null) => void;
|
||||
makeTable: (rowClass?: (index: number) => string | null) => DataTable;
|
||||
redraw: () => void;
|
||||
setTools: (tools: HTMLElement[]) => void;
|
||||
}): HTMLElement[] {
|
||||
const view = chosen.get(o.tab) ?? readGroupView(o.tab);
|
||||
const lead = lineHint(o.hint);
|
||||
@@ -221,17 +223,15 @@ export function renderGroupPanel(o: {
|
||||
focus: at,
|
||||
money: numberFormat(o.spec, "sum"),
|
||||
qty: numberFormat(o.spec, "qty"),
|
||||
lead, // 200-1 안내 글도 같은 줄
|
||||
actions: [toggle], // 185-2 가로 열 수 단추와 한 줄
|
||||
actions: [toggle],
|
||||
placeTools: o.setTools, // 202-1 가로 열 수 · 보기 단추 = 보조 탭 줄 오른쪽
|
||||
});
|
||||
cards.dataset.b09Table = o.tab;
|
||||
return [cards];
|
||||
return [cards, ...(lead ? [lead] : [])];
|
||||
}
|
||||
const bar = document.createElement("div");
|
||||
bar.className = "b09-estimation__bar";
|
||||
bar.append(...(lead ? [lead] : []), toggle);
|
||||
o.setTools([toggle]);
|
||||
const range = pickRange(o.spec.rows, o.focus);
|
||||
const table = o.makeTable((i) => pickClass(range, i));
|
||||
if (at >= 0) requestAnimationFrame(() => table.focusRow(at));
|
||||
return [bar, table.root];
|
||||
return [table.root, ...(lead ? [lead] : [])];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/* 202-1 — 표 칸 아래로 간 안내 줄 몫이 창 끝까지 셈(`ui_template_data_table.ts` fillViewport)에 들어가는지 단위검증 헬퍼.
|
||||
* 카드 본문 = [표 칸][아래 줄][안내 줄 · 안내 줄 …](B08 `tableCard` 꼴) 가짜 DOM 에서 돌리고 JSON 으로 냄. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const file = path.join(__dirname, "..", "..", "ui_template", "ui_template_data_table.ts");
|
||||
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
|
||||
const win = { innerHeight: 900, scrollY: 0, addEventListener() {}, removeEventListener() {} };
|
||||
const observers = [];
|
||||
class RO {
|
||||
constructor(fn) {
|
||||
this.fn = fn;
|
||||
observers.push(this);
|
||||
}
|
||||
observe() {}
|
||||
disconnect() {}
|
||||
}
|
||||
const el = (name, css = {}, height = 0) => ({
|
||||
name,
|
||||
css,
|
||||
height,
|
||||
top: 0,
|
||||
parentElement: null,
|
||||
nextElementSibling: null,
|
||||
isConnected: true,
|
||||
style: {},
|
||||
getBoundingClientRect() {
|
||||
return { top: this.top, height: this.height };
|
||||
},
|
||||
});
|
||||
function link(parent, ...kids) {
|
||||
kids.forEach((k, i) => {
|
||||
k.parentElement = parent;
|
||||
k.nextElementSibling = kids[i + 1] ?? null;
|
||||
});
|
||||
return parent;
|
||||
}
|
||||
const sty = (n) => ({
|
||||
rowGap: n.css.rowGap ?? "normal",
|
||||
paddingBottom: n.css.paddingBottom ?? "0px",
|
||||
borderBottomWidth: n.css.borderBottomWidth ?? "0px",
|
||||
marginBottom: n.css.marginBottom ?? "0px",
|
||||
});
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", "require", "window", "document", "getComputedStyle", "ResizeObserver", js)(
|
||||
box.exports,
|
||||
box,
|
||||
(p) => (p.endsWith(".css") ? {} : require(p)),
|
||||
win,
|
||||
{ body: null },
|
||||
sty,
|
||||
RO,
|
||||
);
|
||||
const m = box.exports;
|
||||
|
||||
// 문서: body(padding-bottom 8) > card(padding-bottom 12 · gap 8) > [root][avg 20][info 30][hint 40]
|
||||
const body = el("body", { paddingBottom: "8px" });
|
||||
const card = el("card", { paddingBottom: "12px", rowGap: "8px" });
|
||||
const root = el("root");
|
||||
const avg = el("avg", {}, 20);
|
||||
const info = el("info", {}, 30);
|
||||
const hint = el("hint", {}, 40);
|
||||
link(body, card);
|
||||
link(card, root, avg, info, hint);
|
||||
root.top = 200;
|
||||
|
||||
m.fillViewport(root);
|
||||
observers.forEach((o) => o.fn());
|
||||
const out = { first: parseFloat(root.style.maxHeight) };
|
||||
out.below = m.spaceBelow(root);
|
||||
out.belowExpected = 3 * 8 + 20 + 30 + 40 + 12 + 8; // 줄 사이 8 셋 · 아래 줄 · 안내 둘 · 카드 · 본문 여백
|
||||
out.bottom = root.top + out.first + out.below; // 문서 맨 아래 = 창 아래 끝(페이지 스크롤 0)
|
||||
out.viewport = win.innerHeight;
|
||||
|
||||
// 안내 줄이 두 줄로 늘어남 → 표 칸이 그만큼 줄어듦
|
||||
hint.height = 60;
|
||||
observers.forEach((o) => o.fn());
|
||||
out.afterHintGrow = parseFloat(root.style.maxHeight);
|
||||
out.firstMinusGrow = out.first - 20;
|
||||
console.log(JSON.stringify(out));
|
||||
@@ -27,5 +27,7 @@ def test_자재_셋_값_없는_줄_숨김():
|
||||
|
||||
|
||||
def test_표_넷_공용_카드_안():
|
||||
assert "createCard({ body: [toolbar, table.root, excel] })" in MATERIAL # + 엑셀 알림(160-4)
|
||||
assert "createCard({ body: parts })" in SUMMARY
|
||||
assert (
|
||||
"createCard({ body: [toolbar, table.root, excel, notes] })" in MATERIAL
|
||||
) # + 엑셀 알림(160-4) · 안내 = 아래(202-1)
|
||||
assert "createCard({ body: [table, ...parts] })" in SUMMARY
|
||||
|
||||
@@ -142,21 +142,21 @@ def test_화면_보기_바꿈_누름_이동():
|
||||
out = run(
|
||||
[COMMON, TABS, f"{B09}Labor.ts", GROUPS],
|
||||
f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});"
|
||||
"\nconst gone = []; let redraws = 0; const focused = [];"
|
||||
"\nconst gone = []; let redraws = 0; const focused = []; const calls = [];"
|
||||
"\nconst o = { tab: 'dg', spec, hint: null, focus: 'DG1|1', goto: (l) => gone.push(l),"
|
||||
" redraw: () => redraws++, makeTable: () => ({ root: document.createElement('table'),"
|
||||
" redraw: () => redraws++, setTools: (t) => calls.push(t), makeTable: () => ({ root: document.createElement('table'),"
|
||||
" focusRow: (i) => focused.push(i) }) };"
|
||||
"\nconst [view] = renderGroupPanel(o);"
|
||||
"\nconst btn = view.querySelector('[data-b09-group-view]');"
|
||||
"\nconst btn = calls[0].find((e) => e.dataset.b09GroupView);"
|
||||
"\nconst label = btn.textContent;"
|
||||
"\nview.querySelector('[data-at=\"2\"]').click();"
|
||||
"\nview.querySelector('[data-at=\"0\"]').click();"
|
||||
"\nconst lit = view.querySelector('[data-at=\"3\"]').classList.contains('is-focus');"
|
||||
"\nconst box = view.querySelector('.ui-group-card').classList.contains('is-pick');"
|
||||
"\nbtn.click();"
|
||||
"\nconst [bar2, view2] = renderGroupPanel(o);"
|
||||
"\nconst [view2] = renderGroupPanel(o);"
|
||||
"\nconsole.log(JSON.stringify({ label, table: view.dataset.b09Table, gone, lit, box, redraws,"
|
||||
" label2: bar2.children[0].textContent, tag2: view2.tagName, focused,"
|
||||
" label2: calls[1].find((e) => e.dataset.b09GroupView).textContent, tag2: view2.tagName, focused,"
|
||||
" kept: Object.fromEntries(kept) }));",
|
||||
RAW,
|
||||
)
|
||||
@@ -171,7 +171,11 @@ def test_화면_보기_바꿈_누름_이동():
|
||||
|
||||
def test_잇기():
|
||||
shell = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Shell.ts").read_text("utf-8")
|
||||
assert "if (isGroupTab(tab)) {" in shell and "renderGroupPanel({ tab, spec," in shell
|
||||
assert (
|
||||
"if (isGroupTab(tab)) {" in shell
|
||||
and "renderGroupPanel({" in shell
|
||||
and "setTools: bars.setTools" in shell
|
||||
)
|
||||
state = (ROOT / "A00_Common" / "b_page_state.ts").read_text("utf-8")
|
||||
assert '"b09-group-view": { bucket: "pref", scope: "global" },' in state
|
||||
assert '"b09-group-cols": { bucket: "pref", scope: "global" },' in state
|
||||
|
||||
@@ -55,7 +55,7 @@ const bars = createTabBars((t) => picks.push(t));
|
||||
const view = () => ({
|
||||
top: bars.top.children.map((b) => [b.textContent, b.className, b.attrs['aria-selected'],
|
||||
b.dataset.b09Group, b.dataset.b09Tab ?? null]),
|
||||
sub: bars.sub.children.map((b) => [b.textContent, b.attrs['aria-selected'], b.dataset.b09Tab ?? null]),
|
||||
sub: bars.sub.children.filter((b) => !b.classList.contains('b09-estimation__tools')).map((b) => [b.textContent, b.attrs['aria-selected'], b.dataset.b09Tab ?? null]),
|
||||
blank: bars.sub.classList.contains('b09-estimation__tabs--blank'),
|
||||
role: bars.sub.attrs.role,
|
||||
});
|
||||
|
||||
@@ -49,7 +49,7 @@ def test_표_보기_줄_class_넘김():
|
||||
[COMMON, TABS, GROUPS],
|
||||
f"\nconst spec = {{ columns: [{{ key: 'key' }}], rows: {json.dumps(ROWS)} }};"
|
||||
"\nlet extra = null;"
|
||||
"\nconst o = { tab: 'dg', spec, hint: null, focus: 'B|1', goto: () => {}, redraw: () => {},"
|
||||
"\nconst o = { tab: 'dg', spec, hint: null, focus: 'B|1', goto: () => {}, redraw: () => {}, setTools: () => {},"
|
||||
" makeTable: (e) => { extra = e; return { root: document.createElement('table'),"
|
||||
" focusRow: () => {} }; } };"
|
||||
"\nchosen.set('dg', 'table');"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""185-2 B09 묶음 탭 도구 줄 — 보기 바꿈 단추 글 한 줄 · 가로 열 수 단추와 같은 줄.
|
||||
"""185-2 B09 묶음 탭 보기 바꿈 단추 글 한 줄 · 가로 열 수 단추와 같은 줄.
|
||||
|
||||
- 카드 보기 = 보기 바꿈 단추가 가로 열 수 줄(`ui-group-cards__bar`) 안 · 표 보기 = 머리 줄(`b09-estimation__bar`)
|
||||
- 단추 · 라벨 = `white-space: nowrap` · 도구 줄 = 줄바꿈 없음(`flex-wrap: nowrap`)
|
||||
- 보기 바꿈 단추 · 가로 열 수 = 도구 묶음(`setTools` · 202-1 로 페이지 보조 탭 줄 오른쪽) · 카드 판 머리 띠 = 묶음 수만
|
||||
- 단추 · 라벨 = `white-space: nowrap` · 도구 묶음 = 줄바꿈 없음(`flex-wrap: nowrap`)
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -27,40 +27,43 @@ def _block(css: str, selector: str) -> str:
|
||||
|
||||
|
||||
def _panel(view: str, hint: str):
|
||||
"""고른 보기 · 안내 글로 그린 뒤 보기 바꿈 단추가 든 줄 · 그 줄의 열 수 단추."""
|
||||
"""고른 보기 · 안내 글로 그린 뒤 도구 묶음(보기 바꿈 단추 · 열 수 단추) · 판 안 머리 띠."""
|
||||
return run(
|
||||
[COMMON, TABS, f"{B09}Labor.ts", GROUPS],
|
||||
f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});"
|
||||
f"\nchosen.set('dg', '{view}');"
|
||||
"\nconst tools = [];"
|
||||
f"\nconst o = {{ tab: 'dg', spec, hint: {hint}, goto: () => {{}}, redraw: () => {{}},"
|
||||
" setTools: (t) => tools.push(...t),"
|
||||
" makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} }) };"
|
||||
"\nconst out = renderGroupPanel(o);"
|
||||
"\nconst top = out.find((e) => e.all('[data-b09-group-view]').length || e.dataset.b09GroupView);"
|
||||
"\nconst btn = top.dataset.b09GroupView ? top : top.querySelector('[data-b09-group-view]');"
|
||||
"\nconst bar = btn.closest('.ui-group-cards__bar') ?? btn.closest('.b09-estimation__bar') ?? top;"
|
||||
"\nconst btn = tools.find((e) => e.dataset.b09GroupView);"
|
||||
"\nconst bar = out[0].all('.ui-group-cards__bar')[0] ?? null;"
|
||||
"\nconsole.log(JSON.stringify({ n: out.length, first: out[0].className, label: btn.textContent,"
|
||||
" bar: bar.className, cols: bar.all('[data-group-cols]').map((b) => b.dataset.groupCols) }));",
|
||||
" cols: tools.flatMap((e) => e.all('[data-group-cols]')).map((b) => b.dataset.groupCols),"
|
||||
" barBtn: bar ? bar.all('[data-b09-group-view]').length : null,"
|
||||
" barCols: bar ? bar.all('[data-group-cols]').length : null }));",
|
||||
RAW,
|
||||
)
|
||||
|
||||
|
||||
def test_카드_보기_단추가_가로_열수_줄에():
|
||||
def test_카드_보기_단추가_열수_단추와_같은_도구_묶음에():
|
||||
out = _panel("cards", "null")
|
||||
assert out["bar"] == "ui-group-cards__bar" and out["label"] == "표로 보기"
|
||||
assert out["cols"] == ["1", "2", "3", "4"] # 같은 줄에 열 수 단추 넷
|
||||
assert out["label"] == "표로 보기"
|
||||
assert out["cols"] == ["1", "2", "3", "4"] # 같은 묶음에 열 수 단추 넷
|
||||
assert out["barBtn"] == 0 and out["barCols"] == 0 # 카드 판 머리 띠에는 없음
|
||||
assert out["n"] == 1 # 안내 글 없으면 카드 판 하나뿐
|
||||
|
||||
|
||||
def test_카드_보기_안내_글은_같은_도구_줄에():
|
||||
# 200-1 — 안내 글이 카드 위 따로 줄에서 도구 줄 안으로(상세 = test_200_1)
|
||||
def test_카드_보기_안내_글은_카드_판_아래에():
|
||||
# 202-1 — 안내 글 = 카드 판 아래(상세 = test_202_1)
|
||||
out = _panel("cards", HINT)
|
||||
assert out["n"] == 1 and out["first"] == "ui-group-cards"
|
||||
assert out["bar"] == "ui-group-cards__bar"
|
||||
assert out["n"] == 2 and out["first"] == "ui-group-cards"
|
||||
|
||||
|
||||
def test_표_보기_단추는_머리_줄():
|
||||
def test_표_보기_단추는_도구_묶음에():
|
||||
out = _panel("table", "null")
|
||||
assert out["bar"] == "b09-estimation__bar" and out["label"] == "박스보기"
|
||||
assert out["label"] == "박스보기" and out["n"] == 1
|
||||
assert out["cols"] == [] # 표 보기 = 열 수 단추 없음
|
||||
|
||||
|
||||
@@ -73,5 +76,9 @@ def test_CSS_한_줄_nowrap():
|
||||
)
|
||||
assert "white-space: nowrap" in item and "flex: none" in item
|
||||
page = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Page.css").read_text("utf-8")
|
||||
assert "white-space: nowrap" in _block(page, ".b09-estimation__bar .ui-btn")
|
||||
assert "flex-wrap: nowrap" in _block(page, ".b09-estimation__tools")
|
||||
tools = _block(
|
||||
page, ".b09-estimation__tools .ui-btn,\n.b09-estimation__tools .ui-group-cards__label"
|
||||
)
|
||||
assert "white-space: nowrap" in tools
|
||||
assert "actions: [toggle]" in (ROOT / GROUPS).read_text("utf-8")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""200-1 B09 박스보기 도구 줄 한 줄 · 카드 같은 가로 줄 높이 같게.
|
||||
"""200-1 B09 박스보기 안내 글 줄임표 + 호버 · 카드 같은 가로 줄 높이 같게.
|
||||
|
||||
- 안내 글 · 가로 열 수 단추 · 보기 단추 = 같은 줄(카드 보기 = `ui-group-cards__bar` · 표 보기 = `b09-estimation__bar`)
|
||||
- 안내 글 = 줄임표 한 줄 · 잘렸을 때만 공용 호버(`attachHoverInfo`)로 전문
|
||||
- 안내 글 = 줄임표 한 줄 · 잘렸을 때만 공용 호버(`attachHoverInfo`)로 전문 · 자리 = 표 · 카드 판 아래(202-1 로 옮김)
|
||||
- 카드 격자 = 행 늘림(`align-items: stretch`) · 카드 = 세로 flex · 몸통이 늘어 합계 줄이 아래
|
||||
- 카드 제목 줄 · 합계 줄 바탕색(200-3)
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -32,43 +32,36 @@ def _block(css: str, selector: str) -> str:
|
||||
|
||||
|
||||
def _panel(view: str, hint: str):
|
||||
"""고른 보기 · 안내 글로 그린 뒤 도구 줄 구성 · 호버 기록."""
|
||||
"""고른 보기 · 안내 글로 그린 뒤 안내 글 자리 · 호버 기록."""
|
||||
return run(
|
||||
[COMMON, TABS, f"{B09}Labor.ts", GROUPS],
|
||||
f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});"
|
||||
f"\nchosen.set('dg', '{view}');"
|
||||
f"\nconst hint = {hint};"
|
||||
"\nconst o = { tab: 'dg', spec, hint, goto: () => {}, redraw: () => {},"
|
||||
"\nconst o = { tab: 'dg', spec, hint, goto: () => {}, redraw: () => {}, setTools: () => {},"
|
||||
" makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} }) };"
|
||||
"\nconst out = renderGroupPanel(o);"
|
||||
"\nconst btn = out.flatMap((e) => (e.dataset.b09GroupView ? [e] : e.all('[data-b09-group-view]')))[0];"
|
||||
"\nconst bar = btn.closest('.ui-group-cards__bar') ?? btn.closest('.b09-estimation__bar');"
|
||||
"\nconst h = hint ? bar.all('.b09-estimation__line-hint')[0] : null;"
|
||||
"\nconst last = out[out.length - 1];"
|
||||
"\nconst inBar = hint ? out.some((e) => e.all('.ui-group-cards__bar').some((b) => b.contains(hint))) : false;"
|
||||
"\nconst call = hoverCalls[0];"
|
||||
"\nconst wide = call ? call.text({ scrollWidth: 900, clientWidth: 300, textContent: 'T' }) : null;"
|
||||
"\nconst fit = call ? call.text({ scrollWidth: 300, clientWidth: 300, textContent: 'T' }) : null;"
|
||||
"\nconsole.log(JSON.stringify({ n: out.length, bar: bar.className,"
|
||||
" cols: bar.all('[data-group-cols]').length, hint: !!h, hintInBar: h ? bar.contains(h) : false,"
|
||||
" hintCls: h ? h.className : '', hover: hoverCalls.length, hoverIsHint: call ? call.root === h : false,"
|
||||
" wide, fit, outer: out.map((e) => e.className) }));",
|
||||
"\nconsole.log(JSON.stringify({ n: out.length, hintLast: !!hint && last === hint, inBar,"
|
||||
" hintCls: hint ? hint.className : '', hover: hoverCalls.length,"
|
||||
" hoverIsHint: call ? call.root === hint : false, wide, fit, first: out[0].tagName }));",
|
||||
RAW,
|
||||
)
|
||||
|
||||
|
||||
def test_카드_보기_안내_글이_단추와_같은_줄():
|
||||
def test_카드_보기_안내_글은_카드_판_아래():
|
||||
out = _panel("cards", HINT)
|
||||
assert out["n"] == 1 # 안내 글 따로 줄 없음 · 카드 판 하나
|
||||
assert out["bar"] == "ui-group-cards__bar"
|
||||
assert out["hint"] and out["hintInBar"] # 열 수 단추 · 보기 단추와 같은 도구 줄 안
|
||||
assert out["cols"] == 4
|
||||
assert out["n"] == 2 and out["hintLast"] and not out["inBar"] # 카드 판 + 안내 글(맨 아래)
|
||||
assert "b09-estimation__line-hint" in out["hintCls"]
|
||||
|
||||
|
||||
def test_표_보기_안내_글이_보기_단추와_같은_줄():
|
||||
def test_표_보기_안내_글은_표_아래():
|
||||
out = _panel("table", HINT)
|
||||
assert out["n"] == 2 # 머리 줄 + 표
|
||||
assert out["bar"] == "b09-estimation__bar"
|
||||
assert out["hint"] and out["hintInBar"]
|
||||
assert out["n"] == 2 and out["first"] == "TABLE" and out["hintLast"] # 표 + 안내 글
|
||||
|
||||
|
||||
def test_안내_글_잘렸을_때만_공용_호버로_전문():
|
||||
@@ -82,7 +75,7 @@ def test_안내_글_잘렸을_때만_공용_호버로_전문():
|
||||
def test_안내_글_없으면_호버도_없음():
|
||||
for view in ("cards", "table"):
|
||||
out = _panel(view, "null")
|
||||
assert out["hint"] is False and out["hover"] == 0
|
||||
assert out["n"] == 1 and out["hintLast"] is False and out["hover"] == 0
|
||||
|
||||
|
||||
def test_CSS_안내_글_줄임표_한_줄():
|
||||
@@ -95,10 +88,6 @@ def test_CSS_안내_글_줄임표_한_줄():
|
||||
"min-width: 0",
|
||||
):
|
||||
assert need in hint, need
|
||||
assert "flex: 1 1 0" in hint
|
||||
# 표 보기 머리 줄 = 줄바꿈 없음 · 같은 높이 가운데
|
||||
bars = re.findall(r"\.b09-estimation__bar\s*\{([^}]*)\}", page)
|
||||
assert any("flex-wrap: nowrap" in b and "align-items: center" in b for b in bars)
|
||||
|
||||
|
||||
def test_CSS_카드_같은_줄_높이_같게():
|
||||
@@ -112,10 +101,9 @@ def test_CSS_카드_같은_줄_높이_같게():
|
||||
|
||||
def test_잇기():
|
||||
groups = (ROOT / GROUPS).read_text("utf-8")
|
||||
assert "lead," in groups and "actions: [toggle]" in groups
|
||||
assert "attachHoverInfo(hint," in groups
|
||||
assert "attachHoverInfo(hint," in groups and "placeTools: o.setTools" in groups
|
||||
cards = (ROOT / COMMON).read_text("utf-8")
|
||||
assert "if (opts.lead) bar.append(opts.lead);" in cards
|
||||
assert "opts.lead" not in cards and "if (opts.placeTools) {" in cards
|
||||
|
||||
|
||||
# ── 200-3 카드 제목 줄 · 합계 줄 바탕색(밝음 · 어두움) ─────────────────────────
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
"""202-1 B08 · B09 표 칸 — 안내 · 경고 줄 = 맨 아래 · 박스보기 · 가로 열 수 = 보조 탭 줄 오른쪽.
|
||||
|
||||
- B09 안내 줄 = 표 · 카드 판 아래(마지막 자식) · 묶음 탭 도구(보기 단추 · 열 수) = 아래 탭 줄 오른쪽 · 단독 탭 빈 줄에서도
|
||||
- B08 안내 · 경고 줄 = 표 · 아래 줄 뒤 맨 아래(토공 · 운반 시트 · 자재 · 구조물 집계)
|
||||
- 창 끝까지 셈(199-1) = 아래로 간 안내 줄 몫 포함
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
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 COMMON, GROUPS, RAW, TABS, run # noqa: E402
|
||||
|
||||
B09 = "B09_Estimation/B09_Estimation_UI_"
|
||||
BAR = f"{B09}TabBar.ts"
|
||||
DG = {"id": "DG1", "no": 1, "name": "찰쌓기", "unit": "m2", "lines": [], "sum": 0}
|
||||
|
||||
|
||||
def _block(css: str, selector: str) -> str:
|
||||
m = re.search(re.escape(selector) + r"\s*\{([^}]*)\}", css)
|
||||
assert m, selector
|
||||
return m.group(1)
|
||||
|
||||
|
||||
# ── B09 보조 탭 줄 오른쪽 도구 ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_도구_자리_보조_줄_오른쪽_빈_줄에서도_보임():
|
||||
out = run(
|
||||
[TABS, BAR],
|
||||
"""
|
||||
const bars = createTabBars(() => {});
|
||||
const mk = (n) => { const b = document.createElement('button'); b.textContent = n; return b; };
|
||||
const snap = () => ({
|
||||
kids: bars.sub.children.map((c) => (c.classList.contains('b09-estimation__tools') ? 'TOOLS' : c.textContent)),
|
||||
last: bars.sub.children[bars.sub.children.length - 1].children.map((x) => x.textContent),
|
||||
});
|
||||
bars.draw('dg');
|
||||
bars.setTools([mk('가'), mk('나')]);
|
||||
const a = snap();
|
||||
bars.draw('compare'); // 다른 묶음으로 옮겨도 도구 자리는 그대로 마지막
|
||||
const b = snap();
|
||||
bars.draw('cost'); // 단독 탭 = 빈 보조 줄
|
||||
const c = snap();
|
||||
const ghost = bars.sub.children[0];
|
||||
bars.setTools([]);
|
||||
const d = snap();
|
||||
console.log(JSON.stringify({
|
||||
a: a.kids, aLast: a.last,
|
||||
b: b.kids.slice(-1), bLast: b.last,
|
||||
c: c.kids, cLast: c.last,
|
||||
blank: bars.sub.classList.contains('b09-estimation__tabs--blank'),
|
||||
subHidden: bars.sub.attrs['aria-hidden'] ?? null, ghostHidden: ghost.attrs['aria-hidden'],
|
||||
dLast: d.last.length,
|
||||
}));
|
||||
""",
|
||||
)
|
||||
tools = "TOOLS"
|
||||
assert out["a"] == ["일위대가목록표", "일위대가표", tools] # 아래 탭 뒤 오른쪽 자리
|
||||
assert out["aLast"] == ["가", "나"]
|
||||
assert out["b"] == [tools] and out["bLast"] == ["가", "나"] # 탭을 옮겨도 도구는 그대로
|
||||
assert out["blank"] and len(out["c"]) == 2 and out["c"][1] == tools # 숨은 단추 + 도구
|
||||
assert out["cLast"] == ["가", "나"]
|
||||
assert out["subHidden"] is None and out["ghostHidden"] == "true" # 줄 전체가 아니라 숨은 단추만
|
||||
assert out["dLast"] == 0 # 비움
|
||||
|
||||
|
||||
def test_묶음_탭_단추가_보조_줄에_안내는_아래에():
|
||||
"""쉘이 하는 대로 — 줄 그림 → 묶음 판 그림(`setTools` = 보조 줄 도구) → 단추는 줄에, 판에는 없음."""
|
||||
out = run(
|
||||
[COMMON, TABS, f"{B09}Tabs_Labor.ts", GROUPS, BAR],
|
||||
f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});"
|
||||
"\nconst bars = createTabBars(() => {});"
|
||||
"\nconst hint = document.createElement('p');"
|
||||
"\nconst res = {};"
|
||||
"\nfor (const view of ['cards', 'table']) {"
|
||||
"\n chosen.set('dg', view);"
|
||||
"\n bars.draw('dg'); bars.setTools([]);"
|
||||
"\n const panel = renderGroupPanel({ tab: 'dg', spec, hint, goto: () => {}, redraw: () => {},"
|
||||
" makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} }),"
|
||||
" setTools: bars.setTools });"
|
||||
"\n const tools = bars.sub.children[bars.sub.children.length - 1];"
|
||||
"\n res[view] = { inRow: tools.all('[data-b09-group-view]').map((b) => b.textContent),"
|
||||
" cols: tools.all('[data-group-cols]').length,"
|
||||
" inPanel: panel.flatMap((e) => [e, ...e.all('[data-b09-group-view]'), ...e.all('[data-group-cols]')])"
|
||||
".filter((e) => e.dataset.b09GroupView || e.dataset.groupCols).length,"
|
||||
" hintLast: panel[panel.length - 1] === hint, n: panel.length };"
|
||||
"\n}"
|
||||
"\nconsole.log(JSON.stringify(res));",
|
||||
RAW,
|
||||
)
|
||||
assert out["cards"]["inRow"] == ["표로 보기"] and out["cards"]["cols"] == 4
|
||||
assert out["table"]["inRow"] == ["박스보기"] and out["table"]["cols"] == 0
|
||||
for view in ("cards", "table"):
|
||||
assert out[view]["inPanel"] == 0 # 판 안에는 단추 없음
|
||||
assert out[view]["hintLast"] and out[view]["n"] == 2 # 판 + 안내 줄(맨 아래)
|
||||
|
||||
|
||||
def test_잇기_쉘_안내_줄_아래_도구_비움():
|
||||
shell = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Shell.ts").read_text("utf-8")
|
||||
assert "panel.replaceChildren(table.root, ...(hint ? [hint] : []))" in shell
|
||||
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
|
||||
|
||||
|
||||
def test_CSS_도구_오른쪽_맞춤_빈_줄_숨김_규칙에_안_걸림():
|
||||
css = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Page.css").read_text("utf-8")
|
||||
tools = _block(css, ".b09-estimation__tools")
|
||||
assert "margin-left: auto" in tools and "flex-wrap: nowrap" in tools
|
||||
hidden = _block(css, ".b09-estimation__tabs--blank > .ui-btn")
|
||||
assert "visibility: hidden" in hidden # 직계 단추만 — 도구 묶음은 안 숨김
|
||||
assert ".b09-estimation__bar" not in css # 옛 머리 줄(안내 + 단추) 풂
|
||||
|
||||
|
||||
# ── B08 안내 · 경고 줄 맨 아래 ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_B08_표_카드_차례_표_아래줄_안내():
|
||||
src = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Earthwork.ts").read_text("utf-8")
|
||||
fn = re.search(r"export function tableCard\(.*?\n}\n", src, re.S)
|
||||
assert fn
|
||||
code = fn.group(0).replace("export ", "")
|
||||
js = (
|
||||
"const createCard = ({ body }) => body;\n"
|
||||
+ code
|
||||
+ "\nconsole.log(JSON.stringify(tableCard(['i1', 'i2'], 'T', ['a1', 'a2'])));"
|
||||
)
|
||||
result = subprocess.run(
|
||||
["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript"],
|
||||
input=js,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == ["T", "a1", "a2", "i1", "i2"] # 표 · 아래 줄 · 안내 줄
|
||||
|
||||
|
||||
def test_B08_자재_구조물_안내_줄_표_아래():
|
||||
material = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Material.ts").read_text("utf-8")
|
||||
assert "createCard({ body: [toolbar, table.root, excel, notes] })" in material
|
||||
assert "toolbar.append(save);" in material # 저장 단추만 위 줄(안내는 아래)
|
||||
summary = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Summary.ts").read_text("utf-8")
|
||||
assert "createCard({ body: [table, ...parts] })" in summary
|
||||
css = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Page.css").read_text("utf-8")
|
||||
assert "justify-content: flex-end" in _block(css, ".b08-material__toolbar")
|
||||
|
||||
|
||||
# ── 창 끝까지 셈(199-1)에 아래 줄 몫 ────────────────────────────────────────
|
||||
|
||||
|
||||
def test_창_끝까지_셈에_아래_안내_줄_포함():
|
||||
proc = subprocess.run(
|
||||
["node", str(ROOT / "resources" / "tester" / "helper_202_1_fill_below.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
r = json.loads(proc.stdout)
|
||||
assert r["below"] == r["belowExpected"] == 134 # 아래 줄 · 안내 줄 둘 + 여백
|
||||
assert r["bottom"] == r["viewport"] # 문서 맨 아래 = 창 끝 → 페이지 스크롤 0
|
||||
assert r["afterHintGrow"] == r["firstMinusGrow"] # 안내 줄이 늘면 표 칸이 그만큼 줄어듦
|
||||
@@ -65,10 +65,10 @@ export interface GroupCardsOptions {
|
||||
focus?: number;
|
||||
money?: (value: number | null | undefined) => string;
|
||||
qty?: (value: number | null | undefined) => string;
|
||||
/** 머리 띠 가운데 안내 글(200-1 · 단추와 한 줄 — 늘어나는 칸) */
|
||||
lead?: HTMLElement | null;
|
||||
/** 머리 띠 끝 단추(예 [표로 보기]) */
|
||||
actions?: HTMLElement[];
|
||||
/** 주면 가로 열 수 · `actions` 를 머리 띠에 안 넣고 이 함수로 넘김(202-1 부르는 쪽 보조 탭 줄 오른쪽) */
|
||||
placeTools?: (tools: HTMLElement[]) => void;
|
||||
}
|
||||
|
||||
export const GROUP_COLUMNS = [1, 2, 3, 4] as const;
|
||||
@@ -210,12 +210,16 @@ export function createGroupCards(opts: GroupCardsOptions): HTMLElement {
|
||||
return b;
|
||||
});
|
||||
pick.append(...colButtons);
|
||||
bar.append(pick, span("ui-group-cards__count", `묶음 ${opts.groups.length}`));
|
||||
if (opts.lead) bar.append(opts.lead);
|
||||
const tail = document.createElement("div");
|
||||
tail.className = "ui-group-cards__actions";
|
||||
tail.append(...(opts.actions ?? []));
|
||||
bar.append(tail);
|
||||
const count = span("ui-group-cards__count", `묶음 ${opts.groups.length}`);
|
||||
if (opts.placeTools) {
|
||||
bar.append(count);
|
||||
opts.placeTools([pick, ...(opts.actions ?? [])]);
|
||||
} else {
|
||||
const tail = document.createElement("div");
|
||||
tail.className = "ui-group-cards__actions";
|
||||
tail.append(...(opts.actions ?? []));
|
||||
bar.append(pick, count, tail);
|
||||
}
|
||||
root.append(bar, grid);
|
||||
|
||||
grid.addEventListener("click", (event) => {
|
||||
|
||||
Reference in New Issue
Block a user