/* ============================================================================= * ui_template_table.ts * 부품 「표」(552-3 · 목록 18) — 머리 줄 + 줄들 · 세로선 없음 · 카드 테두리 · 넘치면 가로 스크롤. * 꼴 = 일반표(줄 40 · 기본) · 데이터표(`density: "data"` · 줄 28 · 글 12) · ⋯ 칸(columns 의 label "" — 줄 끝 `createRowMenu`) * · 한글 배지 칸(칸에 배지 요소) · 오른쪽 맞춤 칸(`align: "end"` · 수) · 빈 줄(`emptyRow`) · 펼침 줄(tr.is-sub) * · 격자표(`grid: true` · 세로선 · 칸마다 테두리 · 가운데 맞춤 · 몸 안 이름 칸 = th 머리색) — 수량 산출표처럼 칸을 합치는 표 * · 합친 칸 = 칸 자리에 `{ content, colSpan, rowSpan, head }`(head = th) · columns 를 비우면 머리 줄 없이 줄로만 그림 * 크기 = 줄 40 / 28 · 머리 36 / 28 * 상태 = 줄 마우스(옅은 바탕) · 고름(tr.is-active / aria-selected) · 바뀜(tr.is-changed) · 지움(tr.is-removed · 줄 그음) * 쓰는 법 = `createTable([{ label: "이름" }, { label: "", width: "44px" }], [["임도 1", createRowMenu([…])]])` * · 줄을 나중에 채우면 `const { root, tbody } = tableShell(columns); tbody.append(tableRow(columns, cells))` * 대신하는 옛 변종 = B01 `dataTable` · `tableShell`(`.b01-pg-table`) · `.b01-proj-table` · `.ui-general-block__table` · D04 · E02 · E03 · E06 · E07 손 표 · M01 · M02 표(목록 4장) * 옛 `.ui-data-table`(끌어 고치는 큰 표 · `ui_template_data_table.ts`)은 따로 그대로. * ========================================================================== */ import { el } from "./ui_template_elements_base"; import "./ui_template_table.css"; /** 합친 칸 · 몸 안 이름 칸(head = th) */ export interface SpanCell { content: string | Node; colSpan?: number; rowSpan?: number; head?: boolean; } export type Cell = string | Node | SpanCell; export interface Column { label: string; /** 칸 너비(CSS) — 없으면 나눠 가짐 */ width?: string; align?: "end"; } export interface TableOptions { /** 데이터표(줄 28 · 글 12) */ density?: "data"; /** 격자표 — 세로선 · 칸 테두리 · 가운데 맞춤 */ grid?: boolean; } /** 표 껍데기 — 머리 + 빈 tbody (줄은 호출 쪽이 채움 · 펼침 줄 같은 것을 끼우려고 떼어 둠) */ export function tableShell( columns: Column[], opts: TableOptions = {}, ): { root: HTMLElement; tbody: HTMLElement } { const table = el("table", { className: `ui-table${opts.density === "data" ? " ui-table--data" : ""}${opts.grid ? " ui-table--grid" : ""}`, }); if (columns.length) { const hr = el("tr"); for (const col of columns) { const th = el("th", { className: col.align ? "is-end" : "", text: col.label }); if (col.width) th.style.width = col.width; hr.append(th); } table.append(el("thead", { children: [hr] })); } const tbody = el("tbody"); table.append(tbody); return { root: el("div", { className: "ui-table-wrap", children: [table] }), tbody }; } /** 한 줄 — 칸 수는 columns 와 같게 · label "" 인 칸 = ⋯ 칸 · 합친 칸은 colSpan 만큼 칸 자리를 건넘 */ export function tableRow(columns: Column[], cells: Cell[]): HTMLTableRowElement { const tr = el("tr"); let i = 0; // ponytail: 위 줄 rowSpan 이 차지한 칸 자리는 안 셈 — 합친 표(격자)는 columns 를 비워 맞춤 · ⋯ 칸 꼴이 필요해지면 자리 표를 둠 for (const cell of cells) { const span = typeof cell === "string" || cell instanceof Node ? null : cell; const col = columns[i]; const td = el(span?.head ? "th" : "td", { className: col?.align ? "is-end" : col?.label === "" ? "ui-table__menucell" : "", }); td.append(span ? span.content : (cell as string | Node)); if (span?.colSpan) td.colSpan = span.colSpan; if (span?.rowSpan) td.rowSpan = span.rowSpan; tr.append(td); i += span?.colSpan ?? 1; } return tr; } /** 빈 줄 — 칸을 다 합쳐 한 줄 안내 */ export function emptyRow(columns: Column[], text: string): HTMLTableRowElement { const td = el("td", { className: "ui-table__empty", text }); td.colSpan = columns.length; return el("tr", { children: [td] }); } /** 표 — 줄이 없으면 `empty` 글 한 줄 */ export function createTable( columns: Column[], rows: Cell[][], opts: TableOptions & { empty?: string } = {}, ): HTMLElement { const { root, tbody } = tableShell(columns, opts); tbody.append(...rows.map((cells) => tableRow(columns, cells))); if (!rows.length && opts.empty) tbody.append(emptyRow(columns, opts.empty)); return root; }