2D: Text/Textspalte auf Inline-Editing direkt auf der Zeichenfläche umgestellt

Nutzer-Report: Textfelder waren nicht mehrzeilig. Ursache: die Text-/
Textspalte-Werkzeuge holten ihre Eingabe über die Befehlszeile
(CommandLine.tsx) — ein einzeiliges <input>, Enter committet sofort, kein
Zeilenumbruch möglich. Das betraf auch "Textspalte", obwohl die explizit für
mehrzeiligen Wortumbruch gedacht ist.

Lösung (Nutzer-Vorgabe: kein Dialog, direktes Editieren auf der Fläche wie
im Layout-System): text.ts/textbox.ts committen nach dem Platzieren (Anker
bzw. Anker+Breite) sofort ein LEERES Text-Element und melden
`focusDrawingId` — dafür CommandResult/EngineHost um einen Passthrough
erweitert (engine.ts applyResult ruft host.focusDrawing(id)). App.tsx setzt
darauf Auswahl + editTextId (dieselbe State-Variable wie beim bisherigen
Doppelklick-Editieren). PlanView rendert bei gesetztem editTextId jetzt
einen RichTextEditor-Overlay direkt über dem Element (position:fixed via
vbToClient/toScreen, eigene Mini-Toolbar erscheint darüber) statt des
bisherigen TextEditorDialog-Modals — Prinzip 1:1 vom Layout-Blatt-Inline-
Editor (LayoutSheet.tsx) übernommen. Die neuen Props (editTextId/
onCommitTextEdit/onCloseTextEdit) laufen durch Content/LevelPlanView/
SectionPlanView (viewportContent.tsx) bis zu PlanView durch.

Verhalten der beiden Werkzeuge (Nutzer-Vorgabe): "text" bleibt ohne
Wortumbruch (kein `width`) — Enter erzeugt weitere Zeilen, aber keine
automatische Breite. "textbox" setzt `width` und verhält sich damit wie ein
InDesign-Textrahmen (echter Wortumbruch live beim Tippen, kein reines
Render-Detail mehr).

commitTextEdit (useMouseSelectionHandlers.ts) schreibt jetzt bei JEDEM
Tastendruck (kein Guard mehr gegen leeren Text — nötig, damit Löschen bis
auf null Zeichen sich sofort spiegelt); neues closeTextEdit räumt ein beim
Schliessen noch leeres Element auf (verhindert Leichen bei abgebrochener
Neuerstellung).

4 neue Tests (text/textbox: sofortiger Commit + focusDrawingId). tsc -b /
vitest run (918/918) / npm run build grün. Rendering/Positionierung des
Overlays selbst nicht automatisiert testbar — Nutzer prüft visuell in der
Tauri-App.
This commit is contained in:
2026-08-20 21:34:40 +02:00
parent 4f48b73267
commit f2d169cc67
11 changed files with 399 additions and 100 deletions
+34
View File
@@ -22,6 +22,7 @@ import type {
} from "../model/types";
import { generatePlan, generateSectionPlan } from "../plan/generatePlan";
import type { CategoryDisplayResolver } from "../plan/generatePlan";
import type { RichTextDoc } from "../text/richText";
import { computeSection } from "../plan/toSection";
import type { SectionOutput } from "../plan/toSection";
import { generateElevationPlan } from "../plan/toElevation";
@@ -91,6 +92,9 @@ export function Content({
onStampMove,
onStampEdit,
onEditDrawingText,
editTextId,
onCommitTextEdit,
onCloseTextEdit,
onPlanSelect,
onPlanMarquee,
onSegmentCut,
@@ -177,6 +181,9 @@ export function Content({
onStampMove: (roomId: string, delta: Vec2) => void;
onStampEdit: (roomId: string) => void;
onEditDrawingText?: (drawingId: string) => void;
editTextId?: string | null;
onCommitTextEdit?: (doc: RichTextDoc) => void;
onCloseTextEdit?: () => void;
onPlanSelect: (sel: PlanSelection) => void;
onPlanMarquee: (sel: MarqueeSelection) => void;
onSegmentCut: (drawingId: string, model: Vec2) => void;
@@ -299,6 +306,9 @@ export function Content({
onStampMove={onStampMove}
onStampEdit={onStampEdit}
onEditDrawingText={onEditDrawingText}
editTextId={editTextId}
onCommitTextEdit={onCommitTextEdit}
onCloseTextEdit={onCloseTextEdit}
onOpenSectionLevel={onSelectLevel}
onPlanSelect={onPlanSelect}
onPlanMarquee={onPlanMarquee}
@@ -358,6 +368,9 @@ export function Content({
onStampMove={onStampMove}
onStampEdit={onStampEdit}
onEditDrawingText={onEditDrawingText}
editTextId={editTextId}
onCommitTextEdit={onCommitTextEdit}
onCloseTextEdit={onCloseTextEdit}
onOpenSectionLevel={onSelectLevel}
onPlanSelect={onPlanSelect}
onPlanMarquee={onPlanMarquee}
@@ -459,6 +472,9 @@ export function Content({
onStampMove={onStampMove}
onStampEdit={onStampEdit}
onEditDrawingText={onEditDrawingText}
editTextId={editTextId}
onCommitTextEdit={onCommitTextEdit}
onCloseTextEdit={onCloseTextEdit}
onOpenSectionLevel={onSelectLevel}
onPlanSelect={onPlanSelect}
onPlanMarquee={onPlanMarquee}
@@ -508,6 +524,9 @@ function LevelPlanView({
onStampMove,
onStampEdit,
onEditDrawingText,
editTextId,
onCommitTextEdit,
onCloseTextEdit,
onOpenSectionLevel,
onPlanSelect,
onPlanMarquee,
@@ -550,6 +569,9 @@ function LevelPlanView({
onStampMove: (roomId: string, delta: Vec2) => void;
onStampEdit: (roomId: string) => void;
onEditDrawingText?: (drawingId: string) => void;
editTextId?: string | null;
onCommitTextEdit?: (doc: RichTextDoc) => void;
onCloseTextEdit?: () => void;
onOpenSectionLevel?: (id: string) => void;
onPlanSelect: (sel: PlanSelection) => void;
onPlanMarquee: (sel: MarqueeSelection) => void;
@@ -631,6 +653,9 @@ function LevelPlanView({
onStampMove={onStampMove}
onStampEdit={onStampEdit}
onEditDrawingText={onEditDrawingText}
editTextId={editTextId}
onCommitTextEdit={onCommitTextEdit}
onCloseTextEdit={onCloseTextEdit}
onOpenSectionLevel={onOpenSectionLevel}
onSelect={onPlanSelect}
onMarquee={onPlanMarquee}
@@ -687,6 +712,9 @@ function SectionPlanView({
onStampMove,
onStampEdit,
onEditDrawingText,
editTextId,
onCommitTextEdit,
onCloseTextEdit,
onOpenSectionLevel,
onPlanSelect,
onPlanMarquee,
@@ -724,6 +752,9 @@ function SectionPlanView({
onStampMove: (roomId: string, delta: Vec2) => void;
onStampEdit: (roomId: string) => void;
onEditDrawingText?: (drawingId: string) => void;
editTextId?: string | null;
onCommitTextEdit?: (doc: RichTextDoc) => void;
onCloseTextEdit?: () => void;
onOpenSectionLevel?: (id: string) => void;
onPlanSelect: (sel: PlanSelection) => void;
onPlanMarquee: (sel: MarqueeSelection) => void;
@@ -831,6 +862,9 @@ function SectionPlanView({
onStampMove={onStampMove}
onStampEdit={onStampEdit}
onEditDrawingText={onEditDrawingText}
editTextId={editTextId}
onCommitTextEdit={onCommitTextEdit}
onCloseTextEdit={onCloseTextEdit}
onOpenSectionLevel={onOpenSectionLevel}
onSelect={onPlanSelect}
onMarquee={onPlanMarquee}