- ui_template_tree: M01 renderTree 를 공용으로 올림(M01 은 같은 이름으로 그대로 부름) · 기본 줄 createTreeRow · 클래스 ui-tree__*
- ui_template_item_picker: openItemPicker({current, canCopy}) → apply(summary_item) | clear | cancel · 왼쪽 마스터 · 회사 · 개인 → 종류 → 분류1 → 항목 나무 + 찾기 · 오른쪽 폴더 모양(키는 툴팁) · 개인용으로 복사 · 관리 화면 이동
- 목록 GET /api/m02/items?all=1 · 복사 POST /api/m02/layers/personal/items/copy — 서버가 없으면(404) 21-1 모양 가짜 응답
- 자료 쪽(나무 · 찾기 · 폴더 줄)은 화면 없는 모듈로 나눠 시험
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K7JS47SacjPxZ718QsZKyr
77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
/* =============================================================================
|
|
* ui_template_tree.ts
|
|
* 공용 나무 보기 — 부모(접기/펴기) · 자식(깊이 들임) · 잎 고름. M01 왼쪽 패널과 항목 고르기가 함께 씀.
|
|
* 줄 모양은 부르는 쪽이 `MakeRow` 로 정함(기본 줄 = `createTreeRow`).
|
|
* ========================================================================== */
|
|
|
|
import "./ui_template_tree.css";
|
|
import { el } from "./ui_template_elements";
|
|
|
|
export interface TreeNode {
|
|
id: string;
|
|
label: string;
|
|
/** 줄 수 — null 이면 안 보임 */
|
|
count: number | null;
|
|
/** 눌렀을 때 — 없으면 접기/펴기만 */
|
|
run?: () => void;
|
|
children?: TreeNode[];
|
|
/** 처음부터 펴짐 */
|
|
open?: boolean;
|
|
/** 마우스를 올렸을 때 글(기본 줄) */
|
|
tip?: string;
|
|
}
|
|
|
|
/** 줄(버튼) 만들기 — 돌려받은 것 = 그 버튼 · click 은 부르는 쪽이 이음 */
|
|
export type MakeRow = (
|
|
node: TreeNode,
|
|
caret: string,
|
|
depth: number,
|
|
onClick: () => void,
|
|
) => HTMLElement;
|
|
|
|
export function renderTree(nodes: TreeNode[], make: MakeRow, depth = 0): HTMLElement[] {
|
|
return nodes.map((node) => {
|
|
if (!node.children?.length) return make(node, "", depth, () => node.run?.());
|
|
const kids = el("div", {
|
|
className: "ui-tree__kids",
|
|
children: renderTree(node.children, make, depth + 1),
|
|
});
|
|
kids.hidden = !node.open;
|
|
const row = make(node, kids.hidden ? "▸" : "▾", depth, () => {
|
|
kids.hidden = !kids.hidden;
|
|
const caret = row.querySelector(".ui-tree__caret");
|
|
if (caret) caret.textContent = kids.hidden ? "▸" : "▾";
|
|
node.run?.();
|
|
});
|
|
return el("div", { className: "ui-tree__branch", children: [row, kids] });
|
|
});
|
|
}
|
|
|
|
/** 기본 줄 — 화살표 · 글 · 줄 수 · 깊이 들임. 잎 고름 표시(`is-active`)는 부르는 쪽이 켬. */
|
|
export function createTreeRow(
|
|
node: TreeNode,
|
|
caret: string,
|
|
depth: number,
|
|
onClick: () => void,
|
|
): HTMLButtonElement {
|
|
const button = el("button", {
|
|
className: "ui-tree__row ui-sidebar-row",
|
|
attrs: { type: "button" },
|
|
children: [
|
|
el("span", {
|
|
children: [
|
|
el("span", { className: "ui-tree__caret", text: caret }),
|
|
el("span", { text: node.label }),
|
|
],
|
|
}),
|
|
...(node.count === null
|
|
? []
|
|
: [el("span", { className: "ui-tree__count", text: node.count.toLocaleString("ko-KR") })]),
|
|
],
|
|
});
|
|
button.style.paddingLeft = `calc(var(--spacing-8) + ${depth} * var(--spacing-16))`;
|
|
if (node.tip) button.title = node.tip;
|
|
button.addEventListener("click", onClick);
|
|
return button;
|
|
}
|