Files
DOSSIER-STANDALONE/src/commands/cmds/textbox.test.ts
T
karim 73d147b37d 2D: Inline-Text-Editor-Nachbesserung (unsichtbarer Anker) + Textspalte als echtes Rechteck
Nutzer-Report nach der Umstellung auf Inline-Editing: beides funktionierte
nicht — "Text" liess sich kein Ankerpunkt setzen, "Textspalte" sollte ein
Rechteck statt nur eine Breite sein.

Root Cause für "Text": `.planview-inline-text` bekam ohne Spaltenbreite
`width: undefined` — bei position:fixed ohne rechten Rand greift shrink-to-
fit-Sizing, das für ein LEERES contentEditable auf ~0px kollabiert. Der Klick
erzeugte tatsächlich ein Element + öffnete den Editor, beides war nur
unsichtbar (eine 0px-Box lässt sich nicht anklicken/fokussieren). Fix: neue
Klasse .planview-inline-text-free (kein wrapWidth) erzwingt `width:
max-content` + `white-space: pre` auf Editor/Surface (Box wächst mit dem
Inhalt statt umzubrechen — "Text" bleibt einzeilig bis Enter, wie gewünscht)
plus eine CSS-Mindestbreite (160px) für den leeren Startzustand.

"Textspalte" (textbox.ts) zeichnet jetzt ein RECHTECK (zwei diagonale Ecken,
beliebige Zugrichtung) statt nur eine horizontale Breitenlinie — die
aufgezogene Höhe wird nicht im Modell gespeichert (das Textformat kennt nur
`width` für den Wortumbruch, keine feste Rahmenhöhe), sondern als
Editor-MINDESThöhe durchgereicht: CommandResult/EngineHost.focusDrawing um
`focusMinHeightM` erweitert (Passthrough wie focusDrawingId), App.tsx hält
sie in einem neuen editTextMinHeightM-State (useContextMenuState.ts),
PlanView nutzt sie als CSS-min-height des Editors — der Rahmen wächst bei
mehr Text darüber hinaus, schrumpft aber nicht darunter (InDesign-Verhalten).
Beim Doppelklick-Editieren bestehender Elemente wird die Mindesthöhe
zurückgesetzt (kein Nachwirken einer vorigen Textspalten-Erstellung).

textbox.test.ts an die neue Rechteck-Geste angepasst (+ Test für beliebige
Zugrichtung). tsc -b / vitest run (919/919) / npm run build grün. Die
eigentliche Sichtbarkeits-Vermutung (CSS-Kollaps) konnte ich nicht
interaktiv im Browser verifizieren (kein Browser-Tool verfügbar) — Nutzer
prüft erneut in der laufenden App.
2026-08-20 21:44:37 +02:00

104 lines
3.4 KiB
TypeScript

/**
* `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 einem aufgezogenen RECHTECK (zwei diagonale Ecken, nicht nur eine
* Breitenlinie) und meldet `focusDrawingId` + `focusMinHeightM` (die
* aufgezogene Rahmenhöhe als Mindesthöhe des Inline-Editors).
*/
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 einem Rechteck (2 Ecken) ein leeres Text-Element mit width + meldet focusDrawingId/focusMinHeightM", () => {
const p = project();
const ctx = makeCtx(p);
const [afterFirst] = textboxCommand.onInput(
textboxCommand.init(),
{ kind: "point", point: { x: 0, y: 5 } },
ctx,
);
// Gegenecke: 3m breit, 1.5m hoch (Rahmen, keine reine Breitenlinie mehr).
const [, result] = textboxCommand.onInput(
afterFirst,
{ kind: "point", point: { x: 3, y: 3.5 } },
ctx,
);
expect(result.done).toBe(true);
expect(result.commit).toBeDefined();
expect(result.focusDrawingId).toBeDefined();
expect(result.focusMinHeightM).toBeCloseTo(1.5, 9);
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(3, 9); // InDesign-artiger Textrahmen: Breite gesetzt
expect(d.geom.at.x).toBeCloseTo(0, 9); // oben-links des Rahmens (unabhängig von der Zugrichtung)
}
});
it("beliebige Zugrichtung (Gegenecke oben-links vom Start): Rahmen wird trotzdem korrekt normalisiert", () => {
const p = project();
const ctx = makeCtx(p);
const [afterFirst] = textboxCommand.onInput(
textboxCommand.init(),
{ kind: "point", point: { x: 5, y: 2 } },
ctx,
);
const [, result] = textboxCommand.onInput(
afterFirst,
{ kind: "point", point: { x: 2, y: 4 } }, // Gegenecke LINKS und OBEN vom Start
ctx,
);
const next = result.commit!(p);
const d = next.drawings2d[0];
if (d.geom.shape === "text") {
expect(d.geom.width).toBeCloseTo(3, 9); // |5-2|
expect(d.geom.at.x).toBeCloseTo(2, 9); // min(x)
}
expect(result.focusMinHeightM).toBeCloseTo(2, 9); // |4-2|
});
});