Raumstempel: Ausrichtung pro Zeile + Anker als Snappunkt

Jede Stempelzeile (Name, Name-2, Bodenflaeche, Nutzung) hat jetzt eine
eigene Ausrichtung links/mitte/rechts, im Stempel-Editor pro Zeile
waehlbar und in SVG- wie nativer Darstellung honoriert (text-anchor bzw.
align). Bodenflaeche/Nutzung fallen auf mitte zurueck (bisheriges Bild).

Der Stempel-Anker (stampAnchor bzw. Schwerpunkt) meldet sich zusaetzlich
als Endpunkt-Snapkandidat an — gefiltert nach Geschoss und sichtbarer
Kategorie; der ziehbare Griff war bereits verdrahtet.
This commit is contained in:
2026-07-03 21:29:13 +02:00
parent af0b044fca
commit f4e70902d0
9 changed files with 224 additions and 50 deletions
+117 -31
View File
@@ -1,5 +1,6 @@
// Schwebender Dialog zum Bearbeiten des strukturierten Raum-Stempels (Feldmodell):
// Raumnummer, Raumname (+ Zeile 2), Bodenfläche (mit Präfix) und Nutzung. Ersetzt
// Raumnummer, Raumname (+ Zeile 2), Bodenfläche (mit Präfix) und Nutzung. Jede
// Zeile trägt zusätzlich eine eigene Ausrichtung (links/mitte/rechts). Ersetzt
// für neue Räume den freien Text-Editor. Kontrolliert (value/onApply/onCancel);
// arbeitet auf einem lokalen Entwurf, erst „Übernehmen" schreibt zurück.
// Bezeichner englisch, UI-Text über t().
@@ -10,6 +11,7 @@
import { useState } from "react";
import { t } from "../i18n";
import type { RoomStamp } from "../model/types";
import type { Align } from "../text/richText";
export interface RoomStampEditorProps {
/** Ausgangsstempel (wird beim Öffnen kopiert; Editor arbeitet lokal). */
@@ -20,11 +22,60 @@ export interface RoomStampEditorProps {
onCancel: () => void;
}
/** Die drei Ausrichtungen mit Icon und Beschriftung (für die Umschalter). */
const ALIGN_CELLS: { align: Align; icon: string; titleKey: string }[] = [
{ align: "left", icon: "format_align_left", titleKey: "text.align.left" },
{ align: "center", icon: "format_align_center", titleKey: "text.align.center" },
{ align: "right", icon: "format_align_right", titleKey: "text.align.right" },
];
/**
* Drei-Wege-Umschalter für die Zeilen-Ausrichtung. `value` ist der aktuelle Wert
* (fehlt er, gilt `fallback`); `onChange` liefert die gewählte Ausrichtung.
*/
function AlignToggle({
value,
fallback,
onChange,
disabled,
label,
}: {
value: Align | undefined;
fallback: Align;
onChange: (align: Align) => void;
disabled?: boolean;
label: string;
}) {
const current = value ?? fallback;
return (
<div className="roomstamp-align" role="group" aria-label={label}>
{ALIGN_CELLS.map((cell) => (
<button
key={cell.align}
type="button"
className={`roomstamp-align-btn${current === cell.align ? " active" : ""}`}
disabled={disabled}
aria-pressed={current === cell.align}
title={t(cell.titleKey)}
aria-label={t(cell.titleKey)}
onClick={() => onChange(cell.align)}
>
<span className="material-symbols-outlined" aria-hidden="true">
{cell.icon}
</span>
</button>
))}
</div>
);
}
export function RoomStampEditor({ value, onApply, onCancel }: RoomStampEditorProps) {
const [draft, setDraft] = useState<RoomStamp>(value);
const patch = (p: Partial<RoomStamp>) => setDraft((d) => ({ ...d, ...p }));
const hasLine2 = (draft.nameLine2 ?? "").trim().length > 0;
return (
<div
className="texteditor-overlay"
@@ -66,34 +117,60 @@ export function RoomStampEditor({ value, onApply, onCancel }: RoomStampEditorPro
/>
</label>
<label className="roomstamp-field">
<span className="roomstamp-label">{t("roomStamp.name")}</span>
<input
type="text"
className="roomstamp-input"
value={draft.name}
onChange={(e) => patch({ name: e.target.value })}
<div className="roomstamp-row">
<label className="roomstamp-field">
<span className="roomstamp-label">{t("roomStamp.name")}</span>
<input
type="text"
className="roomstamp-input"
value={draft.name}
onChange={(e) => patch({ name: e.target.value })}
/>
</label>
<AlignToggle
value={draft.nameAlign}
fallback="left"
label={`${t("roomStamp.name")}${t("text.group")}`}
onChange={(nameAlign) => patch({ nameAlign })}
/>
</label>
</div>
<label className="roomstamp-field">
<span className="roomstamp-label">{t("roomStamp.nameLine2")}</span>
<input
type="text"
className="roomstamp-input"
value={draft.nameLine2 ?? ""}
onChange={(e) => patch({ nameLine2: e.target.value })}
<div className="roomstamp-row">
<label className="roomstamp-field">
<span className="roomstamp-label">{t("roomStamp.nameLine2")}</span>
<input
type="text"
className="roomstamp-input"
value={draft.nameLine2 ?? ""}
onChange={(e) => patch({ nameLine2: e.target.value })}
/>
</label>
<AlignToggle
value={draft.line2Align}
fallback="left"
disabled={!hasLine2}
label={`${t("roomStamp.nameLine2")}${t("text.group")}`}
onChange={(line2Align) => patch({ line2Align })}
/>
</label>
</div>
<label className="roomstamp-check">
<input
type="checkbox"
checked={draft.showFloorArea}
onChange={(e) => patch({ showFloorArea: e.target.checked })}
<div className="roomstamp-row">
<label className="roomstamp-check">
<input
type="checkbox"
checked={draft.showFloorArea}
onChange={(e) => patch({ showFloorArea: e.target.checked })}
/>
<span>{t("roomStamp.showFloorArea")}</span>
</label>
<AlignToggle
value={draft.floorAreaAlign}
fallback="center"
disabled={!draft.showFloorArea}
label={`${t("roomStamp.showFloorArea")}${t("text.group")}`}
onChange={(floorAreaAlign) => patch({ floorAreaAlign })}
/>
<span>{t("roomStamp.showFloorArea")}</span>
</label>
</div>
<label className="roomstamp-field">
<span className="roomstamp-label">{t("roomStamp.floorAreaPrefix")}</span>
@@ -106,14 +183,23 @@ export function RoomStampEditor({ value, onApply, onCancel }: RoomStampEditorPro
/>
</label>
<label className="roomstamp-check">
<input
type="checkbox"
checked={draft.showUsage}
onChange={(e) => patch({ showUsage: e.target.checked })}
<div className="roomstamp-row">
<label className="roomstamp-check">
<input
type="checkbox"
checked={draft.showUsage}
onChange={(e) => patch({ showUsage: e.target.checked })}
/>
<span>{t("roomStamp.showUsage")}</span>
</label>
<AlignToggle
value={draft.usageAlign}
fallback="center"
disabled={!draft.showUsage}
label={`${t("roomStamp.showUsage")}${t("text.group")}`}
onChange={(usageAlign) => patch({ usageAlign })}
/>
<span>{t("roomStamp.showUsage")}</span>
</label>
</div>
</div>
<div className="texteditor-foot">