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
+28 -18
View File
@@ -796,6 +796,25 @@ export default function App() {
setDraft: (d) => cmdLatest.current.setDraft(d),
commit: (mutate) => cmdLatest.current.setProject(mutate),
isFloor: () => cmdLatest.current.activeLevel.kind === "floor",
// Frisch über `text`/`textbox` erzeugtes Element sofort inline editieren
// (kein Lookup nötig — der Commit hat es bereits angehängt, s. engine.ts
// applyResult-Reihenfolge commit→focusDrawing). Auswahl-Löschen wie
// onEditDrawingText (useMouseSelectionHandlers.ts), damit kein anderes
// Element gleichzeitig markiert bleibt.
focusDrawing: (id) => {
setSelectedWallIds([]);
setSelectedCeilingIds([]);
setSelectedOpeningIds([]);
setSelectedStairIds([]);
setSelectedRoomIds([]);
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedContextObjectIds([]);
setSelectedSectionLineId(null);
setSelectedDrawingId(id);
setEditTextId(id);
},
};
engineRef.current = new CommandEngine(host);
}
@@ -2226,6 +2245,7 @@ export default function App() {
onOpenStampEditor,
onEditDrawingText,
commitTextEdit,
closeTextEdit,
onPlanSelect,
onPlanMarquee,
onPlanContextMenu,
@@ -2715,6 +2735,9 @@ export default function App() {
onStampMove={moveRoomStamp}
onStampEdit={onOpenStampEditor}
onEditDrawingText={onEditDrawingText}
editTextId={editTextId}
onCommitTextEdit={commitTextEdit}
onCloseTextEdit={closeTextEdit}
onPlanSelect={onPlanSelect}
onPlanMarquee={onPlanMarquee}
onSegmentCut={onSegmentCut}
@@ -3022,24 +3045,11 @@ export default function App() {
/>
)}
{/* Rich-Text-Editor: per Doppelklick auf ein Text-2D-Element geöffnet (wie
beim Raum-Stempel) — mehrere Absätze, gemischte Formatierung pro
Zeichen (InDesign-/Affinity-artig). Alt-Elemente ohne `doc` starten
aus ihrem bisherigen String + einheitlichen `marks`. */}
{editTextId &&
(() => {
const d = project.drawings2d.find((x) => x.id === editTextId);
if (!d || d.geom.shape !== "text") return null;
const g = d.geom;
const doc = g.doc ?? docFromText(g.text, g.marks ?? {});
return (
<TextEditorDialog
value={doc}
onApply={commitTextEdit}
onCancel={() => setEditTextId(null)}
/>
);
})()}
{/* Inline-Text-Editor für Drawing2D-Text (`editTextId`) rendert PlanView
jetzt direkt auf der Zeichenfläche (kein Dialog mehr, Nutzer-Wunsch —
gleiches Prinzip wie der Layout-Blatt-Inline-Editor). Ausgelöst über
`text`/`textbox` (sofort nach Platzieren, s. host.focusDrawing) oder
Doppelklick auf ein bestehendes Element (onEditDrawingText). */}
{/* Text-Editor (schwebendes Fenster): Doppelklick auf einen Raumstempel bzw.
Button in der Objekt-Info öffnet ihn. „Übernehmen" schreibt das