- 도구줄 createToolbar: data-size 24 · 28 · 32 · 나눔선 · 밀기 · 유리 꼴 · 단 없는 단추에 같은 단을 붙임(552-2 setUiSize) - 떠 있는 판 createFloatPanel: 머리 32 · 닫기 = 아이콘 단추 24(x) · 끌기 머리 · 팝오버 - 오른쪽 서랍: 흰 판 + 1 px 선 · 자리 · 폭 · 열고 닫기 그대로 · 머리 규칙 8 - 부품 모음 틀 아홉 견본 다 참 · 지킴 시험 아홉 부품 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
3.4 KiB
TypeScript
80 lines
3.4 KiB
TypeScript
/* =============================================================================
|
|
* ui_template_float_panel.ts
|
|
* 떠 있는 판 `float-panel`(552-4 틀) — 지도 · 그림 위에 뜨는 작은 판: 머리(제목 · 닫기 ✕) + 몸 · 뒷막 없음.
|
|
* 꼴 = 판(머리 + 몸) · 끌기 머리(draggable · 부모 안에서 옮김) · 팝오버(popover · 머리 없음 · 몸만)
|
|
* 크기 = 머리 data-size 32 · 닫기 = 아이콘 단추 24 · 폭 = width(기본 내용 폭)
|
|
* 상태 = 열림 · 닫힘(hidden) · 끄는 중(`is-dragging`)
|
|
* 쓰는 법 = const fp = createFloatPanel({ title: "측정", onClose: () => fp.root.remove() }); fp.body.append(table); map.append(fp.root)
|
|
* 대신하는 옛 변종 = B05 `b05-routeedit__hint/__info/__measure` · `b05-crossmodal` · `masshaul-overlay` · B06 `b06-structure-panel` ·
|
|
* B08 `b08-milestone__tools` · B09 `.b09-popup` · CAD `.cad-quick-properties` · E03 `e03-view3d` · M02 `m02-pop` · D03 `d03-plan__cross` …
|
|
* ========================================================================== */
|
|
|
|
import { createIconButton } from "./ui_template_button";
|
|
import "./ui_template_float_panel.css";
|
|
|
|
export interface FloatPanelOptions {
|
|
title?: string;
|
|
/** 있으면 머리에 닫기 ✕ */
|
|
onClose?: () => void;
|
|
/** 머리를 잡아 부모 안에서 옮김 */
|
|
draggable?: boolean;
|
|
/** 머리 없는 팝오버 */
|
|
popover?: boolean;
|
|
width?: number;
|
|
}
|
|
|
|
export interface FloatPanel {
|
|
root: HTMLElement;
|
|
body: HTMLElement;
|
|
}
|
|
|
|
/** 머리 끌기 — 부모(위치 잡힌 칸) 안에서 left · top 만 바꿈 */
|
|
function dragBy(root: HTMLElement, head: HTMLElement): void {
|
|
head.addEventListener("pointerdown", (down) => {
|
|
if ((down.target as HTMLElement).closest("button")) return;
|
|
const x0 = down.clientX - root.offsetLeft;
|
|
const y0 = down.clientY - root.offsetTop;
|
|
head.setPointerCapture(down.pointerId);
|
|
root.classList.add("is-dragging");
|
|
const move = (ev: PointerEvent): void => {
|
|
root.style.left = `${Math.max(0, ev.clientX - x0)}px`;
|
|
root.style.top = `${Math.max(0, ev.clientY - y0)}px`;
|
|
};
|
|
const up = (): void => {
|
|
root.classList.remove("is-dragging");
|
|
head.removeEventListener("pointermove", move);
|
|
};
|
|
head.addEventListener("pointermove", move);
|
|
head.addEventListener("pointerup", up, { once: true });
|
|
});
|
|
}
|
|
|
|
export function createFloatPanel(opts: FloatPanelOptions): FloatPanel {
|
|
const root = document.createElement("section");
|
|
root.className = `ui-float-panel${opts.popover ? " ui-float-panel--popover" : ""}`;
|
|
if (opts.width) root.style.width = `${opts.width}px`;
|
|
if (!opts.popover) {
|
|
const head = document.createElement("header");
|
|
head.className = "ui-float-panel__head";
|
|
head.dataset.size = "32";
|
|
const title = document.createElement("strong");
|
|
title.className = "ui-float-panel__title";
|
|
title.textContent = opts.title ?? "";
|
|
head.append(title);
|
|
if (opts.onClose)
|
|
head.append(
|
|
createIconButton({ icon: "x", title: "닫기", size: 24, onClick: () => opts.onClose?.() }),
|
|
);
|
|
if (opts.draggable) {
|
|
head.classList.add("is-draggable");
|
|
dragBy(root, head);
|
|
}
|
|
root.append(head);
|
|
if (opts.title) root.setAttribute("aria-label", opts.title);
|
|
}
|
|
const body = document.createElement("div");
|
|
body.className = "ui-float-panel__body";
|
|
root.append(body);
|
|
return { root, body };
|
|
}
|