feat(M02): 좌측 같은 이름 단추 · 나무 뿌리 줄 없앰 · 「+」 더하기 · 찾기 하나(AND) (PLAN 29-6)

- 「일위대가 조합」 · 「구조물 조합」 컨테이너 안 같은 이름 줄 단추 없앰 — 제목을 펼치면 그 화면을 엶
- 나무 맨 위 뿌리 줄 없앰 — 폴더 · 항목부터 · 맨 위 더하기는 빈 자리 오른쪽 클릭 · 층 단추 옆 「+」(읽기만 층 = 숨김) · 빈 나무 안내글 맞춤
- 찾기 칸 하나 — 띄어쓰기 낱말 모두(AND · 대소문자 무시) · 이름 · 키 · 경로(구분 · 상세구분) · 단위 · 공용 matchText 를 나무 · 일위대가 조합 목록이 함께 씀(목록의 따로 찾기 칸 없앰)
- 시험 — 찾기 AND · 새 배치 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 18:15:43 +09:00
co-authored by Claude Opus 5.5
parent 95849aefbd
commit c1ad11e869
14 changed files with 127 additions and 51 deletions
@@ -47,8 +47,6 @@ export interface TreePanelOptions {
layer: Layer;
/** 남의 층 주인(회사 `{C}` · 개인 `{C}/{U}`) — 없으면 자기 층 */
owner?: string | null;
/** 맨 위 줄 글 — 없으면 층 이름 */
label?: string;
/** 나무 읽기 · 고치기 길 — 없으면 구조물 조합 나무(`/layers/{층}/tree`) */
api?: {
fetch: (layer: Layer, owner: string | null) => Promise<TreeView>;
@@ -73,6 +71,8 @@ export interface TreePanelHandle {
root: HTMLElement;
/** 찾기 칸 — 부르는 쪽이 상세 위쪽에 붙임(PLAN 29-2) */
search: HTMLElement;
/** 「+」 — 맨 위 폴더 · 항목 더하기 · 부르는 쪽이 층 단추 옆에 붙임(읽기만 층 = 숨김 · 29-6) */
add: HTMLElement;
refresh: () => Promise<void>;
/** 표에서 고른 열을 나무에 표시(null = 지움) */
select: (colId: string | null) => void;
@@ -84,15 +84,6 @@ const why = (error: unknown): string => (error instanceof Error ? error.message
const hint = (text: string): HTMLElement => el("p", { className: "m02-tree__hint", text });
export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
const layerLabel =
opt.label ??
L(
opt.layer === "company"
? "M02_PickerCompany"
: opt.layer === "personal"
? "M02_PickerPersonal"
: "M02_PickerSystem",
);
const owner = opt.owner ?? null;
const api = opt.api ?? { fetch: fetchTree, edit: editTree };
let view: TreeView | null = null;
@@ -107,6 +98,16 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
const body = el("div", { className: "m02-tree__body", attrs: { tabindex: "-1" } });
const root = el("div", { className: "m02-tree", children: [body] });
search.root.classList.add("m02-tree__search");
// 층 단추 옆 「+」 — 맨 위에 폴더 · 항목 더하기(뿌리 줄 오른쪽 클릭을 갈음)
const add = el("button", {
className: "m02-tree__add",
text: "+",
attrs: { type: "button", title: L("M02_TreeAddTop"), "aria-label": L("M02_TreeAddTop") },
});
add.hidden = !opt.canEdit;
add.addEventListener("click", (event) =>
openTreeMenu(event, menuOf({ kind: "folder", path: [] })),
);
/* --- 그리기 --- */
const menuOf = (target: Target): TreeMenuItem[] => {
@@ -158,7 +159,7 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
return node(
{ kind: "folder", path: folder.path },
{
label: folder.path.length ? folder.path[folder.path.length - 1] : layerLabel,
label: folder.path[folder.path.length - 1] ?? "", // 맨 위(빈 경로)는 그리지 않음
count: countItems(folder),
folder: true,
children: kids,
@@ -184,7 +185,8 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
const tree = buildFolders(view, text);
const none = countItems(tree) === 0 && (searching || !tree.folders.length);
body.replaceChildren(
...renderTree([folderNode(tree, searching)], make),
// 맨 위 뿌리 줄 없음 — 폴더 · 항목부터(PLAN 29-6 · 뿌리 메뉴 = 빈 자리 오른쪽 클릭 · 「+」)
...renderTree(folderNode(tree, searching).children ?? [], make),
...(none
? [
hint(
@@ -401,5 +403,5 @@ export function createTreePanel(opt: TreePanelOptions): TreePanelHandle {
}
});
return { root, search: search.root, refresh, select };
return { root, search: search.root, add, refresh, select };
}