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:
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* `textboxCommand` — committet seit der Umstellung auf Inline-Editing (kein
|
||||
* Dialog/keine Befehlszeilen-Eingabe mehr, InDesign-artiges Textrahmen-
|
||||
* Verhalten, Nutzer-Wunsch) SOFORT ein leeres Text-Element MIT Spaltenbreite
|
||||
* nach Anker+Breite und meldet `focusDrawingId`.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { textboxCommand } from "./textbox";
|
||||
import type { CommandContext, Project } from "../types";
|
||||
|
||||
function project(): Project {
|
||||
return {
|
||||
id: "t",
|
||||
name: "T",
|
||||
lineStyles: [],
|
||||
hatches: [],
|
||||
components: [],
|
||||
wallTypes: [],
|
||||
drawingLevels: [
|
||||
{ id: "eg", name: "EG", kind: "floor", visible: true, locked: false, floorHeight: 2.6, cutHeight: 1.0, baseElevation: 0 },
|
||||
],
|
||||
layers: [{ code: "20", name: "Zeichnung", color: "#0a0a0a", lw: 0.5, visible: true, locked: false }],
|
||||
walls: [],
|
||||
doors: [],
|
||||
openings: [],
|
||||
ceilings: [],
|
||||
stairs: [],
|
||||
rooms: [],
|
||||
drawings2d: [],
|
||||
context: [],
|
||||
};
|
||||
}
|
||||
|
||||
function makeCtx(p: Project): CommandContext {
|
||||
return {
|
||||
project: p,
|
||||
level: p.drawingLevels[0],
|
||||
defaultCategoryCode: "20",
|
||||
activeLineStyleId: "solid",
|
||||
activeWallTypeId: "aw",
|
||||
lastPoint: null,
|
||||
selection: { wallIds: [], drawingId: null },
|
||||
};
|
||||
}
|
||||
|
||||
describe("textboxCommand", () => {
|
||||
it("committet nach Anker+Breite sofort ein leeres Text-Element mit width und meldet focusDrawingId", () => {
|
||||
const p = project();
|
||||
const ctx = makeCtx(p);
|
||||
|
||||
const [afterPoint] = textboxCommand.onInput(
|
||||
textboxCommand.init(),
|
||||
{ kind: "point", point: { x: 0, y: 0 } },
|
||||
ctx,
|
||||
);
|
||||
const [, result] = textboxCommand.onInput(
|
||||
afterPoint,
|
||||
{ kind: "point", point: { x: 2, y: 0 } }, // Spaltenbreite = 2m
|
||||
ctx,
|
||||
);
|
||||
expect(result.done).toBe(true);
|
||||
expect(result.commit).toBeDefined();
|
||||
expect(result.focusDrawingId).toBeDefined();
|
||||
|
||||
const next = result.commit!(p);
|
||||
expect(next.drawings2d.length).toBe(1);
|
||||
const d = next.drawings2d[0];
|
||||
expect(d.id).toBe(result.focusDrawingId);
|
||||
expect(d.geom.shape).toBe("text");
|
||||
if (d.geom.shape === "text") {
|
||||
expect(d.geom.text).toBe("");
|
||||
expect(d.geom.width).toBeCloseTo(2, 9); // InDesign-artiger Textrahmen: Breite gesetzt
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user