Merge remote-tracking branch 'origin/sub_laptop_6' into main_laptop_1
This commit is contained in:
@@ -11,18 +11,15 @@
|
||||
* 자유 · 길이 고정 · 기울기 고정(기울기 고정 · 길이 자유 · 마우스 방향 첫 자리) · 둘 다(양각기 원 위 후보 중
|
||||
* 마우스에 가까운 것) · 제약 기울기 = 오르내림 같은 크기(562-1 짝이 |기울기|로 셈).
|
||||
* 긋기는 해를 안 물음(585-3) — 새 선 = 사업연도 시작 해 · 갈래 = 붙은 자리 구간 해 · 이어 긋기 = 끝 구간이 늘어남 ·
|
||||
* 여러 해 = 다 그은 뒤 도구줄 「연도 마커」를 선 위로 끌어 구역을 나누고 속성 판에서 해를 고름.
|
||||
* 여러 해 = 다 그은 뒤 그리기 칸 「연도 구역 마커」를 선 위로 끌어 구역을 나누고 속성 판에서 해를 고름.
|
||||
* 고르기 · 고치기 · 마커(쉴 때) = `D03_PlanAnalysis_UI_Edit`.
|
||||
* 키 = Enter 끝 · Esc 이번 긋기 취소 · Backspace 마지막 점 빼기(설정 창 단축키 표 D03 줄).
|
||||
* 셈 = 562-1 짝(`logic/D03_PlanAnalysis_Draw`) · 꼴 다루기 = `logic/D03_PlanAnalysis_Plan` ·
|
||||
* 값을 바꾸면 `commit(다음 꼴)` 한 번 = 되돌리기 한 걸음(스택 · 캐시 = 페이지).
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, showToast } from "@ui/ui_template_elements";
|
||||
import { showToast } from "@ui/ui_template_elements";
|
||||
import { keyIs } from "../A00_Common/key_registry";
|
||||
import { createInputField } from "@ui/ui_template_input";
|
||||
import { createSegment } from "@ui/ui_template_segment";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import {
|
||||
compassCandidates,
|
||||
compassPoint,
|
||||
@@ -50,17 +47,19 @@ import {
|
||||
paintNetwork,
|
||||
paintPreview,
|
||||
screenGap,
|
||||
slopeDeg,
|
||||
SNAP_PX,
|
||||
} from "./D03_PlanAnalysis_UI_DrawPaint";
|
||||
import {
|
||||
createDrawKindFields,
|
||||
createDrawPanel,
|
||||
DRAW_DEFAULT_LENGTH_M,
|
||||
DRAW_DEFAULT_SLOPE_PCT,
|
||||
type DrawConstraint,
|
||||
} from "./D03_PlanAnalysis_UI_DrawPanel";
|
||||
import type { PlanMapLayer, ScreenMap } from "./D03_PlanAnalysis_UI_Map";
|
||||
|
||||
/** 제약 칸 처음 값 — 길이 = 측점 간격(지식DB 설계제원_총괄) · 기울기 = 간선 20 km/h 일반지형 상한(종단선형). */
|
||||
export const DRAW_DEFAULT_LENGTH_M = 20;
|
||||
export const DRAW_DEFAULT_SLOPE_PCT = 9;
|
||||
const START_HINT = "지점 · 선 끝 · 선 위를 눌러 시작합니다 — 빈 땅은 우클릭 「여기서 긋기 시작」.";
|
||||
|
||||
export type DrawConstraint = "free" | "length" | "slope" | "both";
|
||||
export type DrawMode = "idle" | "draw";
|
||||
|
||||
export interface DrawPreview {
|
||||
@@ -87,7 +86,7 @@ export interface PlanDrawParams {
|
||||
|
||||
export interface PlanDraw {
|
||||
layer: PlanMapLayer;
|
||||
/** 좌측 「그리기」 칸 몸(모드 단추 · 제약 칸 · 안내). */
|
||||
/** 좌측 「그리기」 칸 몸(`D03_PlanAnalysis_UI_DrawPanel` — 긋기 · 연도 구역 마커 · 방식 칸). */
|
||||
panel: HTMLElement;
|
||||
mode: () => DrawMode;
|
||||
setMode: (mode: DrawMode) => void;
|
||||
@@ -119,94 +118,23 @@ export function createPlanDraw(params: PlanDrawParams): PlanDraw {
|
||||
let slopePct = DRAW_DEFAULT_SLOPE_PCT;
|
||||
|
||||
// ── 좌측 칸 ─────────────────────────────────────────────
|
||||
const drawButton = createButton({
|
||||
label: "긋기",
|
||||
variant: "filled",
|
||||
size: 28,
|
||||
onClick: () => setMode(mode === "draw" ? "idle" : "draw"),
|
||||
const kindFields = createDrawKindFields({
|
||||
value: { kind, lengthM, slopePct },
|
||||
onChange: (value) => ({ kind, lengthM, slopePct } = value),
|
||||
});
|
||||
const modeRow = document.createElement("div");
|
||||
modeRow.className = "d03-plan__row";
|
||||
modeRow.append(drawButton);
|
||||
const kindSegment = createSegment({
|
||||
options: [
|
||||
{ value: "free", label: "자유" },
|
||||
{ value: "length", label: "길이 고정" },
|
||||
{ value: "slope", label: "기울기 고정" },
|
||||
{ value: "both", label: "둘 다" },
|
||||
],
|
||||
value: kind,
|
||||
size: 24,
|
||||
ariaLabel: "긋기 제약",
|
||||
onChange: (value) => {
|
||||
kind = value as DrawConstraint;
|
||||
fieldsState();
|
||||
},
|
||||
const drawPanel = createDrawPanel({
|
||||
kind: kindFields,
|
||||
onDraw: () => setMode(mode === "draw" ? "idle" : "draw"),
|
||||
});
|
||||
kindSegment.root.dataset.role = "draw-constraint";
|
||||
const lengthField = createInputField({
|
||||
label: "길이",
|
||||
type: "number",
|
||||
value: String(lengthM),
|
||||
min: 1,
|
||||
unit: "m",
|
||||
size: 24,
|
||||
onInput: (value) => {
|
||||
const next = Number(value);
|
||||
if (next > 0) lengthM = next;
|
||||
lengthField.setError(next > 0 ? undefined : "0 보다 큰 수");
|
||||
},
|
||||
});
|
||||
lengthField.input.dataset.role = "draw-length";
|
||||
const slopeField = createInputField({
|
||||
label: "기울기(± 같음)",
|
||||
type: "number",
|
||||
value: String(slopePct),
|
||||
unit: "%",
|
||||
size: 24,
|
||||
onInput: (value) => {
|
||||
const next = Number(value);
|
||||
const ok = value.trim() !== "" && Number.isFinite(next);
|
||||
if (ok) slopePct = next;
|
||||
slopeField.setError(ok ? undefined : "숫자");
|
||||
degText();
|
||||
},
|
||||
});
|
||||
slopeField.input.dataset.role = "draw-slope";
|
||||
const slopeDegText = createText("", "muted");
|
||||
slopeDegText.dataset.role = "draw-slope-deg";
|
||||
const degText = (): void => {
|
||||
slopeDegText.textContent = `≈ ${fmt(slopeDeg(Math.abs(slopePct)))}°`;
|
||||
};
|
||||
degText();
|
||||
const fieldRow = document.createElement("div");
|
||||
fieldRow.className = "d03-plan__row";
|
||||
fieldRow.append(lengthField.root, slopeField.root, slopeDegText);
|
||||
const hint = createText("", "muted");
|
||||
hint.classList.add("d03-plan__hint");
|
||||
const panel = document.createElement("div");
|
||||
panel.className = "d03-plan__draw";
|
||||
panel.append(modeRow, kindSegment.root, fieldRow, hint);
|
||||
|
||||
function fieldsState(): void {
|
||||
lengthField.input.disabled = kind === "free" || kind === "slope";
|
||||
slopeField.input.disabled = kind === "free" || kind === "length";
|
||||
}
|
||||
const panel = drawPanel.root;
|
||||
|
||||
function panelState(): void {
|
||||
drawButton.querySelector(".ui-btn__label")!.textContent = mode === "draw" ? "긋기 끝" : "긋기";
|
||||
drawButton.disabled = !editable;
|
||||
drawButton.setAttribute("aria-pressed", String(mode === "draw"));
|
||||
panel.dataset.mode = mode;
|
||||
hint.textContent = !editable
|
||||
? "보기만 — 산림청 계정에서 긋습니다."
|
||||
: mode === "draw"
|
||||
? active === null
|
||||
? START_HINT
|
||||
: "누름 = 점 · Enter 끝 · Esc 취소 · Backspace 마지막 점 빼기"
|
||||
: "[긋기]로 지점 · 선에서 긋습니다 · 지도 우클릭 = 긋기 시작 · 지점 놓기 · 선 누름 = 고름 · 「연도 마커」를 선 위로 끌면 구역을 나눕니다.";
|
||||
drawPanel.state({
|
||||
drawing: mode === "draw",
|
||||
editable,
|
||||
canMark: params.network().lines.some((line) => line.vertices.length > 1),
|
||||
});
|
||||
}
|
||||
fieldsState();
|
||||
|
||||
// ── 셈 ──────────────────────────────────────────────────
|
||||
const lastOf = (line: number): Vertex | null => {
|
||||
@@ -524,13 +452,9 @@ export function createPlanDraw(params: PlanDrawParams): PlanDraw {
|
||||
constraint: () => ({ kind, lengthM, slopePct }),
|
||||
setConstraint(next, length, slope) {
|
||||
kind = next;
|
||||
kindSegment.set(next);
|
||||
if (length !== undefined) [lengthM, lengthField.input.value] = [length, String(length)];
|
||||
if (slope !== undefined) {
|
||||
[slopePct, slopeField.input.value] = [slope, String(slope)];
|
||||
degText();
|
||||
}
|
||||
fieldsState();
|
||||
if (length !== undefined) lengthM = length;
|
||||
if (slope !== undefined) slopePct = slope;
|
||||
kindFields.set({ kind, lengthM, slopePct });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
/* =============================================================================
|
||||
* D03_PlanAnalysis_UI_DrawPanel.ts
|
||||
* D03 좌측 「그리기」 칸 몸(PLAN 599-1 · 599-4 · 요구 463-10 · 22) — [긋기] · 연도 구역 마커 · 긋기 방식 칸.
|
||||
*
|
||||
* 방식 칸 `createDrawKindFields` = 마디 고름 2 × 2(자유 · 길이 고정 · 기울기 고정 · 길이·기울기 고정) +
|
||||
* 길이 · 기울기 두 열(기울기 아래 ≈ °) — 그리기 칸 · 노드 판(598-4)이 같은 부품을 씀.
|
||||
* 연도 구역 마커 = 끌어 선 위에 놓기(`YEAR_MARK_DRAG` · 받음 = `D03_PlanAnalysis_UI_Edit` drop).
|
||||
* 설명 글 없음(요구 463-6) — 칸 오류 · ≈ ° 값만.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { UiSize } from "@ui/ui_template_button";
|
||||
import { createButton } from "@ui/ui_template_elements";
|
||||
import { createInputField } from "@ui/ui_template_input";
|
||||
import { createSegment } from "@ui/ui_template_segment";
|
||||
import { createText } from "@ui/ui_template_text";
|
||||
import { fmt, slopeDeg } from "./D03_PlanAnalysis_UI_DrawPaint";
|
||||
import { YEAR_MARK_DRAG } from "./D03_PlanAnalysis_UI_Map";
|
||||
|
||||
/** 제약 칸 처음 값 — 길이 = 측점 간격(지식DB 설계제원_총괄) · 기울기 = 간선 20 km/h 일반지형 상한(종단선형). */
|
||||
export const DRAW_DEFAULT_LENGTH_M = 20;
|
||||
export const DRAW_DEFAULT_SLOPE_PCT = 9;
|
||||
|
||||
export type DrawConstraint = "free" | "length" | "slope" | "both";
|
||||
|
||||
export interface DrawKindValue {
|
||||
kind: DrawConstraint;
|
||||
lengthM: number;
|
||||
slopePct: number;
|
||||
}
|
||||
|
||||
export const DRAW_KIND_LABELS: Record<DrawConstraint, string> = {
|
||||
free: "자유",
|
||||
length: "길이 고정",
|
||||
slope: "기울기 고정",
|
||||
both: "길이·기울기 고정",
|
||||
};
|
||||
|
||||
export interface DrawKindFields {
|
||||
root: HTMLElement;
|
||||
get: () => DrawKindValue;
|
||||
/** 값을 바꿈(onChange 는 안 부름). */
|
||||
set: (value: Partial<DrawKindValue>) => void;
|
||||
setDisabled: (disabled: boolean) => void;
|
||||
}
|
||||
|
||||
/** 긋기 방식 칸 — 고름 · 길이 · 기울기가 바뀔 때마다 `onChange(지금 값)`(빈 · 틀린 수는 칸 오류만). */
|
||||
export function createDrawKindFields(opts: {
|
||||
value: DrawKindValue;
|
||||
onChange: (value: DrawKindValue) => void;
|
||||
size?: UiSize;
|
||||
}): DrawKindFields {
|
||||
const size = opts.size ?? 24;
|
||||
let value = { ...opts.value };
|
||||
let disabled = false;
|
||||
const changed = (): void => opts.onChange({ ...value });
|
||||
const segment = createSegment({
|
||||
options: (Object.keys(DRAW_KIND_LABELS) as DrawConstraint[]).map((kind) => ({
|
||||
value: kind,
|
||||
label: DRAW_KIND_LABELS[kind],
|
||||
})),
|
||||
value: value.kind,
|
||||
size,
|
||||
columns: 2,
|
||||
ariaLabel: "긋기 방식",
|
||||
onChange: (next) => {
|
||||
value.kind = next as DrawConstraint;
|
||||
fieldsState();
|
||||
changed();
|
||||
},
|
||||
});
|
||||
segment.root.dataset.role = "draw-constraint";
|
||||
const lengthField = createInputField({
|
||||
label: "길이",
|
||||
type: "number",
|
||||
value: String(value.lengthM),
|
||||
min: 1,
|
||||
unit: "m",
|
||||
size,
|
||||
onInput: (text) => {
|
||||
const next = Number(text);
|
||||
lengthField.setError(next > 0 ? undefined : "0 보다 큰 수");
|
||||
if (!(next > 0)) return;
|
||||
value.lengthM = next;
|
||||
changed();
|
||||
},
|
||||
});
|
||||
lengthField.input.dataset.role = "draw-length";
|
||||
const slopeField = createInputField({
|
||||
label: "기울기",
|
||||
type: "number",
|
||||
value: String(value.slopePct),
|
||||
unit: "%",
|
||||
size,
|
||||
onInput: (text) => {
|
||||
const next = Number(text);
|
||||
const ok = text.trim() !== "" && Number.isFinite(next);
|
||||
slopeField.setError(ok ? undefined : "숫자");
|
||||
if (!ok) return;
|
||||
value.slopePct = next;
|
||||
degText();
|
||||
changed();
|
||||
},
|
||||
});
|
||||
slopeField.input.dataset.role = "draw-slope";
|
||||
const slopeDegText = createText("", "muted");
|
||||
slopeDegText.dataset.role = "draw-slope-deg";
|
||||
const degText = (): void => {
|
||||
slopeDegText.textContent = `≈ ${fmt(slopeDeg(Math.abs(value.slopePct)))}°`;
|
||||
};
|
||||
const slopeColumn = document.createElement("div");
|
||||
slopeColumn.append(slopeField.root, slopeDegText);
|
||||
const fields = document.createElement("div");
|
||||
fields.className = "d03-plan__draw-fields";
|
||||
fields.append(lengthField.root, slopeColumn);
|
||||
const root = document.createElement("div");
|
||||
root.className = "d03-plan__draw-kind";
|
||||
root.append(segment.root, fields);
|
||||
|
||||
function fieldsState(): void {
|
||||
for (const button of segment.root.querySelectorAll("button")) button.disabled = disabled;
|
||||
lengthField.input.disabled = disabled || value.kind === "free" || value.kind === "slope";
|
||||
slopeField.input.disabled = disabled || value.kind === "free" || value.kind === "length";
|
||||
}
|
||||
degText();
|
||||
fieldsState();
|
||||
|
||||
return {
|
||||
root,
|
||||
get: () => ({ ...value }),
|
||||
set(next) {
|
||||
value = { ...value, ...next };
|
||||
segment.set(value.kind);
|
||||
lengthField.input.value = String(value.lengthM);
|
||||
slopeField.input.value = String(value.slopePct);
|
||||
lengthField.setError(undefined);
|
||||
slopeField.setError(undefined);
|
||||
degText();
|
||||
fieldsState();
|
||||
},
|
||||
setDisabled(next) {
|
||||
disabled = next;
|
||||
fieldsState();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface DrawPanel {
|
||||
root: HTMLElement;
|
||||
/** 단추 글 · 막힘 — 긋는 중 · 고칠 수 있음 · 마커 놓을 선 있음. */
|
||||
state: (state: { drawing: boolean; editable: boolean; canMark: boolean }) => void;
|
||||
}
|
||||
|
||||
/** 좌측 「그리기」 칸 — [긋기] · 연도 구역 마커(끌기) · 방식 칸. */
|
||||
export function createDrawPanel(opts: { kind: DrawKindFields; onDraw: () => void }): DrawPanel {
|
||||
const drawButton = createButton({
|
||||
label: "긋기",
|
||||
variant: "filled",
|
||||
size: 28,
|
||||
onClick: opts.onDraw,
|
||||
});
|
||||
const markButton = createButton({
|
||||
label: "연도 구역 마커",
|
||||
variant: "ghost",
|
||||
size: 28,
|
||||
iconStart: "flag",
|
||||
});
|
||||
markButton.title = "선 위로 끌어 놓으면 구역을 나눕니다";
|
||||
markButton.dataset.role = "draw-year-mark";
|
||||
markButton.draggable = true;
|
||||
markButton.addEventListener("dragstart", (event) => {
|
||||
event.dataTransfer?.setData(YEAR_MARK_DRAG, "1");
|
||||
if (event.dataTransfer) event.dataTransfer.effectAllowed = "copy";
|
||||
});
|
||||
const row = document.createElement("div");
|
||||
row.className = "d03-plan__row";
|
||||
row.append(drawButton, markButton);
|
||||
const root = document.createElement("div");
|
||||
root.className = "d03-plan__draw";
|
||||
root.append(row, opts.kind.root);
|
||||
return {
|
||||
root,
|
||||
state({ drawing, editable, canMark }) {
|
||||
drawButton.querySelector(".ui-btn__label")!.textContent = drawing ? "긋기 끝" : "긋기";
|
||||
drawButton.disabled = !editable;
|
||||
drawButton.setAttribute("aria-pressed", String(drawing));
|
||||
markButton.disabled = !editable || drawing || !canMark;
|
||||
root.dataset.mode = drawing ? "draw" : "idle";
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -35,7 +35,7 @@ import type { GlBase } from "./D03_PlanAnalysis_UI_MapGl";
|
||||
import type { Vertex } from "./logic/D03_PlanAnalysis_Plan";
|
||||
import { localTmFrame, tmParams } from "./logic/D03_PlanAnalysis_Tm";
|
||||
|
||||
/** 도구줄 「연도 마커」 끌어 놓기 자료 꼴(585-3). */
|
||||
/** 「연도 구역 마커」 끌어 놓기 자료 꼴(585-3 · 단추 = 그리기 칸 599-4). */
|
||||
export const YEAR_MARK_DRAG = "application/x-aislo-year-mark";
|
||||
|
||||
/** 측점 간격(m) — B 규칙 측점과 같음. */
|
||||
@@ -124,7 +124,7 @@ export interface PlanTools {
|
||||
view3d: boolean;
|
||||
/** 거리재기 켜짐(눌림). */
|
||||
measure: boolean;
|
||||
/** 「연도 마커」 끌 수 있음(고칠 수 있고 선 있음). */
|
||||
/** 받기만 — 연도 구역 마커 단추는 그리기 칸(599-4 · `D03_PlanAnalysis_UI_DrawPanel`)이 켬끔. */
|
||||
yearMark: boolean;
|
||||
}
|
||||
|
||||
@@ -197,12 +197,6 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
|
||||
const redoButton = mapToolButton("redo", { onClick: events.onRedo });
|
||||
const viewButton = mapToolButton("view3d", { onClick: events.on3d });
|
||||
const measureButton = mapToolButton("measure", { onClick: events.onMeasure });
|
||||
const yearMarkButton = mapToolButton("year-mark", { disabled: true });
|
||||
yearMarkButton.draggable = true;
|
||||
yearMarkButton.addEventListener("dragstart", (event) => {
|
||||
event.dataTransfer?.setData(YEAR_MARK_DRAG, "1");
|
||||
if (event.dataTransfer) event.dataTransfer.effectAllowed = "copy";
|
||||
});
|
||||
const tools = createMapTools({
|
||||
label: "평면 도구",
|
||||
hint: {
|
||||
@@ -216,7 +210,7 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
|
||||
"Shift+누름 = 구간 더함 · 뺌",
|
||||
"빈 곳 오른쪽으로 끌기 = 창 안 구간 · 왼쪽으로 = 걸친 구간까지",
|
||||
"고른 선 위 끌기 = 꺾임점 더해 옮김",
|
||||
"「연도 마커」 선 위로 끌어 놓기 = 구역 나눔",
|
||||
"그리기 칸 「연도 구역 마커」 선 위로 끌어 놓기 = 구역 나눔",
|
||||
"꺾임점 · 마크 끌기 = 옮김 · 마크를 선 밖으로 = 지움(앞 구간과 합침)",
|
||||
"오른쪽 누름 = 꺾임점 · 마크 지움",
|
||||
"Delete = 고른 선 · 마크 지움",
|
||||
@@ -229,7 +223,6 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
|
||||
[undoButton, redoButton],
|
||||
[viewButton, "rotate-ccw", "rotate-cw"],
|
||||
[markButton("stations", "측점"), markButton("slopes", "구간 길이 · 기울기"), measureButton],
|
||||
[yearMarkButton],
|
||||
],
|
||||
});
|
||||
const toolbar = tools.root;
|
||||
@@ -526,7 +519,6 @@ export function createPlanMap(events: PlanMapEvents): PlanMap {
|
||||
if (tools.view3d !== undefined) viewButton.disabled = !tools.view3d;
|
||||
if (tools.measure !== undefined)
|
||||
measureButton.setAttribute("aria-pressed", String(tools.measure));
|
||||
if (tools.yearMark !== undefined) yearMarkButton.disabled = !tools.yearMark;
|
||||
},
|
||||
redraw: drawTop,
|
||||
redrawBase: draw,
|
||||
|
||||
@@ -195,3 +195,21 @@
|
||||
.d03-plan__piece-year {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 599 그리기 칸 — 방식 칸(마디 고름 2 × 2 · 길이 · 기울기 두 열) */
|
||||
.d03-plan__draw-kind {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.d03-plan__draw-fields {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--spacing-8);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.d03-plan__draw-fields .ui-field {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -30,11 +30,12 @@ def test_d03_parts():
|
||||
three = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_3D.ts")
|
||||
css = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Style.css")
|
||||
draw = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Draw.ts")
|
||||
draw_panel = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_DrawPanel.ts")
|
||||
tools = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Map.ts")
|
||||
layers = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Layers.ts")
|
||||
assert 'createElement("progress")' not in ts and "createProgressBar()" in ts
|
||||
# 562-3 — 날 단추 · 입력 · 고르기 없음 · 도구줄 · 제약 칸 · 연도 고름은 부품
|
||||
for src in (ts, draw, tools, layers):
|
||||
for src in (ts, draw, draw_panel, tools, layers):
|
||||
for raw in ('createElement("button")', 'createElement("input")', 'createElement("select")'):
|
||||
assert raw not in src, raw
|
||||
# 568-3 — 도구줄 = 공용 지도 도구줄 · 아홉([?] · 되돌리기 · 다시하기 · 3D · 돌리기 둘 · 측점 · 기울기 · 재기)
|
||||
@@ -76,7 +77,14 @@ def test_d03_parts():
|
||||
("place", "false"),
|
||||
], keys
|
||||
assert ".d03-plan__layers" in css and ".d03-plan__legend" in css
|
||||
assert "createSegment({" in draw and draw.count("createInputField(") == 2
|
||||
# 599-1 — 방식 칸 = 마디 고름 2 × 2 · 길이 · 기울기 두 열 · 노드 판(598-4)과 같은 함수
|
||||
assert "createSegment({" in draw_panel and draw_panel.count("createInputField(") == 2
|
||||
assert "columns: 2" in draw_panel and "export function createDrawKindFields(" in draw_panel
|
||||
assert ".d03-plan__draw-fields" in css and "createDrawKindFields(" in draw
|
||||
assert '"길이·기울기 고정"' in draw_panel and '"둘 다"' not in draw_panel
|
||||
assert "d03-plan__hint" not in draw # 칸 설명 글 걷음(요구 463-6)
|
||||
# 599-4 — 연도 구역 마커 = 그리기 칸 단추(끌어 놓기) · 지도 도구줄에서 뺌
|
||||
assert "YEAR_MARK_DRAG" in draw_panel and '"year-mark"' not in tools
|
||||
props = _src("D03_PlanAnalysis/D03_PlanAnalysis_UI_Props.ts") # 585-2 조각 연도 = 속성 판
|
||||
assert "createSelectField(" in props and "createFloatPanel(" in props
|
||||
assert 'createElement("button")' not in props and 'createElement("select")' not in props
|
||||
|
||||
@@ -55,7 +55,6 @@ def test_단추_목록_아이콘_차례():
|
||||
"footprint",
|
||||
"measure",
|
||||
"station-snap",
|
||||
"year-mark",
|
||||
]
|
||||
names = _icon_names()
|
||||
assert all(icon in names for _, icon, _ in rows)
|
||||
|
||||
@@ -31,7 +31,6 @@ export const MAP_TOOLS = {
|
||||
footprint: { icon: "road", title: "절/성토선", toggle: true },
|
||||
measure: { icon: "ruler", title: "거리 재기", toggle: true },
|
||||
"station-snap": { icon: "magnet", title: "측점 스냅", toggle: true },
|
||||
"year-mark": { icon: "flag", title: "연도 구역 마커 — 선 위로 끌어 놓기" },
|
||||
} satisfies Record<string, { icon: IconName; title: string; toggle?: boolean }>;
|
||||
|
||||
export type MapToolAct = keyof typeof MAP_TOOLS;
|
||||
|
||||
@@ -48,6 +48,19 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 여러 줄(columns) — 칸 사이 1px 틈에 틀 색 바탕이 보여 줄 · 칸 나눔선 */
|
||||
.ui-segment[data-columns] {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--ui-segment-columns), 1fr);
|
||||
gap: 1px;
|
||||
height: auto;
|
||||
background: var(--color-border);
|
||||
}
|
||||
.ui-segment[data-columns] > .ui-segment__item {
|
||||
height: var(--ui-size-h);
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
/* 숨김 — display 가 [hidden] 을 덮지 않게(공용 기본 꼴이 안 실린 곳 · 페이지 꾸밈 위에서도) */
|
||||
:is(.ui-segment, .ui-segment__item)[hidden] {
|
||||
display: none !important;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*
|
||||
* 꼴 = 글 · 아이콘(+ title) · 글 + 아이콘
|
||||
* 크기 = data-size 20 · 24 · 28 · 32(기본 28)
|
||||
* 줄 = 한 줄(기본) · `columns: 2` = 2 × 2 처럼 여러 줄 칸(긴 이름 넷 · 599-1 D03 긋기 방식)
|
||||
* 상태 = 마우스 `:hover` · 눌림 `:active` · 막힘 `disabled` · 고름 = aria-checked — CSS 는 `[data-state]` 로도 받음
|
||||
* 쓰기 = `createSegment({ options: [{ value: "list", label: "목록" }, { value: "card", label: "카드" }], value: "list", onChange })`
|
||||
* 대신 = B01 `.b01-sys-seg` · `.b01-home__seg` · `.b01-proj-seg` · B04 `b04-surface__sheet-method` · B05 `b05-routerec__pills` ·
|
||||
@@ -37,12 +38,18 @@ export function createSegment(opts: {
|
||||
/** 20 · 24 · 28 · 32 권함 */
|
||||
size?: UiSize;
|
||||
ariaLabel?: string;
|
||||
/** 한 줄 칸 수 — 넘으면 다음 줄(없음 = 한 줄) */
|
||||
columns?: number;
|
||||
}): SegmentHandle {
|
||||
const root = document.createElement("div");
|
||||
root.className = "ui-segment";
|
||||
root.setAttribute("role", "radiogroup");
|
||||
if (opts.ariaLabel) root.setAttribute("aria-label", opts.ariaLabel);
|
||||
setUiSize(root, opts.size ?? 28);
|
||||
if (opts.columns) {
|
||||
root.dataset.columns = String(opts.columns);
|
||||
root.style.setProperty("--ui-segment-columns", String(opts.columns));
|
||||
}
|
||||
let current = opts.value;
|
||||
const items = opts.options.map((o) => {
|
||||
const b = document.createElement("button");
|
||||
|
||||
Reference in New Issue
Block a user