feat(M02): 「구조물 조합」 제목 줄 오른쪽 끝 「새 항목」 (PLAN 30-5 구조물 쪽)

- 쓸 수 있는 층만 · 구조물 조합일 때만 보임 · 나무 「항목 추가」와 같은 창 · 고른 폴더 안(없으면 맨 위)
- 공용 모양 newItemButton · m02-main__new(일위대가 「새 조합」이 같은 자리 · 모양을 씀)
- 시험 한 건 · 머리 줄 시험 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 19:23:39 +09:00
co-authored by Claude Opus 5.5
parent 62073aac1b
commit dc725012ed
7 changed files with 63 additions and 7 deletions
@@ -107,6 +107,8 @@ export interface TreePanelHandle {
refresh: () => Promise<void>;
/** 표에서 고른 열을 나무에 표시(null = 지움) */
select: (colId: string | null) => void;
/** 「새 항목」 — 고른 폴더 안(없으면 맨 위)에 항목 추가 창 */
newItem: () => void;
}
const why = (error: unknown): string => (error instanceof Error ? error.message : "");
@@ -672,5 +674,11 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
}
});
return { root, search: search.root, add: tools, refresh, select };
/** 상세 제목 줄 「새 항목」(30-5) — 나무 「항목 추가」와 같은 창 · 고른 폴더(하나)가 있으면 그 안 · 아니면 맨 위 */
const newItem = (): void => {
const at = anchor && picked.has(anchor) ? targets.get(anchor) : undefined;
void addItem(at?.kind === "folder" ? at.path : []);
};
return { root, search: search.root, add: tools, refresh, select, newItem };
}
@@ -106,13 +106,23 @@ export function canEdit(layer: Layer, isAdmin: boolean): boolean {
}
/** 페이지가 끼우는 것 — `top` = 편집 칸 맨 위 막대(회사용 읽기만 안내) · `onColumn` = 집계표에서 고른 열 ·
* `search` = 「구조물 조합」을 열 때만 상세 위쪽에 보이는 나무 찾기 칸(나무는 좌측 패널 · PLAN 29-2) */
* `search` = 「구조물 조합」을 열 때만 상세 위쪽에 보이는 나무 찾기 칸(나무는 좌측 패널 · PLAN 29-2) ·
* `newItem` = 「구조물 조합」 제목 줄 오른쪽 끝 「새 항목」(쓸 수 있는 층만 · PLAN 30-5 · 공용 `m02-main__new`) */
export interface MainExtras {
top?: HTMLElement;
search?: HTMLElement;
newItem?: HTMLElement;
onColumn?: (column: string | null) => void;
}
/** 상세 제목 줄 오른쪽 끝 「새 …」 단추 — 공용 모양 `m02-main__new`(일위대가 「새 조합」과 같게 · 30-5) */
export function newItemButton(run: () => void, label = L("M02_TreeNewItem")): HTMLElement {
const button = createButton({ label, variant: "filled", size: "sm" });
button.classList.add("m02-main__new");
button.addEventListener("click", run);
return button;
}
/** `onOpened` — 무엇을 열든(좌측 · 카드 [도면 수정] · 다시 읽기) 연 바로 뒤 부름(좌측 고름 표시) */
export function createMain(
isAdmin: boolean,
@@ -133,7 +143,10 @@ export function createMain(
className: "m02-main",
children: [
...(extras.top ? [extras.top] : []),
el("div", { className: "m02-main__head", children: [title, back, slot, reload, save] }),
el("div", {
className: "m02-main__head",
children: [title, back, slot, reload, save, ...(extras.newItem ? [extras.newItem] : [])],
}),
...(extras.search ? [extras.search] : []),
notice,
host,
@@ -143,9 +156,10 @@ export function createMain(
const root = column;
const isCombo = (at: Selection | null): boolean =>
!!at && at.kind === "table" && at.name === STRUCTURE_TABLE && at.layer !== "project";
// 찾기 칸은 「구조물 조합」일 때만
// 찾기 칸 · 「새 항목」은 「구조물 조합」일 때만
const showAside = (): void => {
if (extras.search) extras.search.hidden = !isCombo(sel);
if (extras.newItem) extras.newItem.hidden = !isCombo(sel);
};
let sel: Selection | null = null;
@@ -36,7 +36,7 @@ import {
createPeopleTree,
seesOthers,
} from "./M02_MasterTemplete_TreePanel_Roots";
import { canEdit, createMain, type Selection } from "./M02_MasterTemplete_UI_Main";
import { canEdit, createMain, newItemButton, type Selection } from "./M02_MasterTemplete_UI_Main";
import { buildSide } from "./M02_MasterTemplete_UI_Side";
import "./M02_MasterTemplete_UI_Style.css";
@@ -129,6 +129,8 @@ export async function renderM02MasterTemplate(root: HTMLElement): Promise<Mounte
{
top: readOnlyBar?.root,
search: tree.search,
// 「새 항목」(30-5) — 제목 줄 오른쪽 끝 · 쓸 수 있는 층만 · 나무 「항목 추가」와 같은 창
...(canEdit(layer, isAdmin) ? { newItem: newItemButton(() => tree.newItem()) } : {}),
onColumn: (column) => {
readOnlyBar?.setColumn(column);
tree.select(column);
@@ -226,4 +226,13 @@
.m02-main__head + .m02-tree__search {
margin-bottom: var(--spacing-8);
}
}
/* 상세 제목 줄 오른쪽 끝 「새 항목」 · 「새 조합」(PLAN 30-5 · 공용) — 늘어나는 칸이 없어도 끝으로 */
.m02-main__new {
margin-left: auto;
}
.m02-main__new[hidden] {
display: none;
}