Files
Aislo/ui_template/ui_template_overlay.ts
T
eomsangdonandClaude Opus 5 77cb381a2d fix(B05): 배수유역 패널 기본 펼침 + 접기 화살표 방향 정정
상세 배수유역 정보는 페이지에 들어오면 바로 보여야 하는데 기본값이 접힘이었고,
접기/펼치기 화살표도 반대를 가리키고 있었다.

- 기본값: 저장값이 없을 때 `!== "false"`(접힘)를 `=== "true"`(펼침)로 바꿨다.
  같은 페이지 하단 종단 패널(B05_wf2_Route_UI_Profile_Panel.ts:592)이 이미 쓰던 규칙과
  같아졌다.
- 화살표: 공용 `createWorkflowPanelHandle`은 왼쪽에 도킹해 왼쪽으로 접히는 패널
  (제목 카드)을 전제로 회전값을 고정하고 있었다. 배수유역 패널은 오른쪽에 도킹해
  오른쪽으로 접히므로 같은 회전을 쓰면 열기/닫기 표시가 정확히 거꾸로 나온다.
  `collapseToward` 인자를 추가하고 이 패널만 "right"를 넘긴다.

기본값 "left"라 나머지 호출처는 회전값이 이전과 동일하다:
- ui_template_overlay.ts:81 제목 카드 — "side" 기본값, 열림 180° / 닫힘 0° 그대로
- B05_wf2_Route_UI_Profile_Panel.ts:244 하단 패널 — "bottom" 배치라 애초에 무관

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 09:48:15 +09:00

166 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import "./ui_template_overlay.css";
import { t } from "./ui_template_locale";
const TITLE_OVERLAY_STATE_KEY = "frd_workflow_title_overlay_open";
const PROGRESS_OVERLAY_STATE_KEY = "frd_workflow_progress_overlay_open";
export interface WorkflowOverlayOptions {
title: string;
progressContent: HTMLElement;
optionsContent?: HTMLElement;
showTitlePanel?: boolean;
onOptionsOpenChange?: (isOpen: boolean) => void;
}
export interface WorkflowOverlayHandle {
root: HTMLElement;
setTitleOpen: (isOpen: boolean) => void;
setProgressOpen: (isOpen: boolean) => void;
}
export interface WorkflowPanelHandle {
root: HTMLButtonElement;
setOpen: (isOpen: boolean) => void;
}
export function createWorkflowPanelHandle(
placement: "side" | "bottom",
/**
* 패널이 접히면서 사라지는 쪽. 화살표가 항상 그 방향을 가리키게 만든다.
*
* 기본값 `"left"`는 왼쪽에 도킹해 왼쪽으로 접히는 패널(제목 카드)용이다.
* 오른쪽에 도킹해 오른쪽으로 접히는 패널(B05 배수유역도)은 `"right"`를 넘겨야
* 화살표가 뒤집히지 않는다 — 도킹 방향이 반대인데 같은 회전을 쓰면 열기/닫기
* 표시가 정확히 거꾸로 보인다.
*/
collapseToward: "left" | "right" = "left",
): WorkflowPanelHandle {
const root = document.createElement("button");
root.type = "button";
root.className = `ui-workflow-overlay__handle ui-workflow-overlay__handle--${placement}`;
const icon = document.createElement("span");
icon.className = "ui-workflow-overlay__handle-icon";
root.append(icon);
function setOpen(isOpen: boolean): void {
// 삼각형은 회전 0°에서 오른쪽(▶)을 가리킨다. 펼친 상태면 접히는 쪽을,
// 접힌 상태면 펼쳐질 쪽을 가리키도록 180°를 붙였다 뗀다.
const pointsRight = isOpen === (collapseToward === "right");
const rotation = placement === "side" ? (pointsRight ? 0 : 180) : isOpen ? 90 : -90;
icon.style.transform = `rotate(${rotation}deg)`;
root.title = t(isOpen ? "Workflow_Overlay_Collapse" : "Workflow_Overlay_Expand");
root.setAttribute("aria-label", root.title);
root.setAttribute("aria-expanded", String(isOpen));
}
return { root, setOpen };
}
function readOpenState(key: string): boolean {
return sessionStorage.getItem(key) !== "false";
}
function createPanel(
variant: "title" | "progress",
titleText: string,
body: HTMLElement,
storageKey: string,
onOpenChange?: (isOpen: boolean) => void,
): { root: HTMLElement; setOpen: (isOpen: boolean) => void } {
const root = document.createElement("aside");
root.className = `ui-workflow-overlay__panel ui-workflow-overlay__panel--${variant}`;
// title 패널은 사이드바 도킹형 — 토글이 패널 우측 가장자리 세로 중앙 핸들로 나감
const isSidebar = variant === "title";
const header = document.createElement("div");
header.className = "ui-workflow-overlay__header";
const title = document.createElement("h2");
title.className = "ui-workflow-overlay__title";
title.textContent = titleText;
const panelHandle = isSidebar ? createWorkflowPanelHandle("side") : null;
const toggle = panelHandle?.root ?? document.createElement("button");
if (!panelHandle) {
toggle.type = "button";
toggle.className = "ui-workflow-overlay__toggle";
}
if (isSidebar) {
header.append(title);
root.append(header, toggle);
} else {
header.append(title, toggle);
root.append(header);
}
if (body.childElementCount > 0) {
body.classList.add("ui-workflow-overlay__body");
root.append(body);
}
function setOpen(isOpen: boolean): void {
root.classList.toggle("is-collapsed", !isOpen);
if (panelHandle) panelHandle.setOpen(isOpen);
else {
toggle.textContent = isOpen ? "" : "+";
toggle.title = t(isOpen ? "Workflow_Overlay_Collapse" : "Workflow_Overlay_Expand");
toggle.setAttribute("aria-label", toggle.title);
toggle.setAttribute("aria-expanded", String(isOpen));
}
sessionStorage.setItem(storageKey, String(isOpen));
onOpenChange?.(isOpen);
}
function toggleOpen(): void {
setOpen(root.classList.contains("is-collapsed"));
}
title.tabIndex = 0;
title.setAttribute("role", "button");
title.addEventListener("click", toggleOpen);
title.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
toggleOpen();
}
});
toggle.addEventListener("click", toggleOpen);
setOpen(readOpenState(storageKey));
return { root, setOpen };
}
export function createWorkflowOverlays(options: WorkflowOverlayOptions): WorkflowOverlayHandle {
const root = document.createElement("div");
root.className = "ui-workflow-overlay";
const progressBody = document.createElement("div");
progressBody.append(options.progressContent);
const progressPanel = createPanel(
"progress",
t("Workflow_Progress_Title"),
progressBody,
PROGRESS_OVERLAY_STATE_KEY,
);
let setTitleOpen = (_isOpen: boolean): void => {};
if (options.showTitlePanel !== false) {
const titleBody = document.createElement("div");
if (options.optionsContent) titleBody.append(options.optionsContent);
const titlePanel = createPanel(
"title",
options.title,
titleBody,
TITLE_OVERLAY_STATE_KEY,
options.onOptionsOpenChange,
);
root.append(titlePanel.root);
setTitleOpen = titlePanel.setOpen;
}
root.append(progressPanel.root);
return {
root,
setTitleOpen,
setProgressOpen: progressPanel.setOpen,
};
}