Zeilenhöhe-Regler statt "+ Text"; Linienstil editierbar; DOSSIER-Audit gesichert
- "+ Text"-Knopf entfernt (war ohnehin immer deaktiviert, kein Werkzeug dahinter — Text wird ein eigenständiges Zeichenwerkzeug, AUDIT B1). An seiner Stelle ein Zeilenhöhe-Regler (Stepper, Vielfaches der Schriftgrösse) — neues Paragraph.lineHeight, durchgereicht bis in HTML-Vorschau (line-height) und SVG-Render (kumulierte Zeilen- Vorschübe statt festem lineGap). - Linienstil im Attribute-Panel war rein informativ (kein Setter im Host-Kontrakt). onSetSelectionLineStyle ergänzt (nur Drawing2D), jetzt echtes Dropdown statt "—"-Anzeige. - docs/design/dossier-feature-audit.md: der ausführliche A1-A6/B1-B4/ C1-C3/D1-D3/E-Auditbericht aus einer alten Session gesichert (lag bisher nur im Transkript, nicht im Repo) — Quelle der HANDOVER.md- AUDIT-Kürzel.
This commit is contained in:
+5
-1
@@ -2156,6 +2156,11 @@ export default function App() {
|
||||
else if (selection?.kind === "drawing2d")
|
||||
setElementFill(selection.id, hatchId);
|
||||
},
|
||||
onSetSelectionLineStyle: (lineStyleId) => {
|
||||
// Nur Drawing2D trägt einen Linienstil (Wand/Decke/etc. nicht).
|
||||
if (selection?.kind === "drawing2d")
|
||||
updateDrawing2D(selection.id, { lineStyleId });
|
||||
},
|
||||
onSetSelectionFillColor: (color) => {
|
||||
// Vollton-Füllfarbe nur bei Drawing2D (geschlossene Formen).
|
||||
if (selection?.kind === "drawing2d")
|
||||
@@ -3061,7 +3066,6 @@ export default function App() {
|
||||
/>
|
||||
}
|
||||
textTarget={textTarget}
|
||||
onAddText={null}
|
||||
/>
|
||||
|
||||
<div className="body">
|
||||
|
||||
+5
-2
@@ -212,6 +212,7 @@ export const de = {
|
||||
"attr.color": "Farbe",
|
||||
"attr.weight": "Strichstärke (mm)",
|
||||
"attr.lineStyle": "Linienstil",
|
||||
"attr.lineStyle.none": "Standard",
|
||||
"attr.fill": "Füllung",
|
||||
"attr.fillColor": "Füllfarbe",
|
||||
"attr.clearFill": "Füllfarbe entfernen",
|
||||
@@ -882,8 +883,10 @@ export const de = {
|
||||
"text.align.left": "Linksbündig",
|
||||
"text.align.center": "Zentriert",
|
||||
"text.align.right": "Rechtsbündig",
|
||||
"text.add": "Text",
|
||||
"text.add.hint": "Text-Werkzeug — neuen Text platzieren (bald verfügbar)",
|
||||
"text.lineHeight": "Zeilenhöhe",
|
||||
"text.lineHeight.hint": "Zeilenhöhe (Vielfaches der Schriftgrösse)",
|
||||
"text.lineHeight.increase": "Zeilenhöhe erhöhen",
|
||||
"text.lineHeight.decrease": "Zeilenhöhe verringern",
|
||||
"text.editTitle": "Text bearbeiten",
|
||||
"text.apply": "Übernehmen",
|
||||
"text.cancel": "Abbrechen",
|
||||
|
||||
+5
-2
@@ -211,6 +211,7 @@ export const en: Record<TranslationKey, string> = {
|
||||
"attr.color": "Color",
|
||||
"attr.weight": "Line weight (mm)",
|
||||
"attr.lineStyle": "Line style",
|
||||
"attr.lineStyle.none": "Default",
|
||||
"attr.fill": "Fill",
|
||||
"attr.fillColor": "Fill color",
|
||||
"attr.clearFill": "Remove fill color",
|
||||
@@ -874,8 +875,10 @@ export const en: Record<TranslationKey, string> = {
|
||||
"text.align.left": "Align left",
|
||||
"text.align.center": "Center",
|
||||
"text.align.right": "Align right",
|
||||
"text.add": "Text",
|
||||
"text.add.hint": "Text tool — place new text (coming soon)",
|
||||
"text.lineHeight": "Line height",
|
||||
"text.lineHeight.hint": "Line height (multiple of font size)",
|
||||
"text.lineHeight.increase": "Increase line height",
|
||||
"text.lineHeight.decrease": "Decrease line height",
|
||||
"text.editTitle": "Edit text",
|
||||
"text.apply": "Apply",
|
||||
"text.cancel": "Cancel",
|
||||
|
||||
@@ -199,10 +199,24 @@ export function AttributesPanel() {
|
||||
</>,
|
||||
)}
|
||||
|
||||
{/* Linienstil — read-only/informativ (kein Setter im Kontrakt). */}
|
||||
{/* Linienstil — nur Drawing2D trägt ihn (Wand/Decke/etc. read-only). */}
|
||||
<span className="attr-key">{t("attr.lineStyle")}</span>
|
||||
<span className="attr-val attr-readonly">
|
||||
{lineStyle ? lineStyle.name : "—"}
|
||||
<span className="attr-val">
|
||||
{isDrawing ? (
|
||||
<Dropdown
|
||||
value={drawing?.lineStyleId ?? "__none__"}
|
||||
onChange={(v) =>
|
||||
host.onSetSelectionLineStyle(v === "__none__" ? undefined : v)
|
||||
}
|
||||
width={120}
|
||||
options={[
|
||||
{ value: "__none__", label: t("attr.lineStyle.none") },
|
||||
...project.lineStyles.map((l) => ({ value: l.id, label: l.name })),
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<span className="attr-readonly">{lineStyle ? lineStyle.name : "—"}</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -152,6 +152,11 @@ export interface PanelHostValue {
|
||||
* die Quelle, siehe {@link onSetSelectionHatchSource}).
|
||||
*/
|
||||
onSetSelectionFill: (hatchId: string | null) => void;
|
||||
/**
|
||||
* Setzt/entfernt den Linienstil-Override — wirkt NUR auf Drawing2D (sonst
|
||||
* No-op). `undefined` löscht den Wert (fällt zurück auf den Default-Stil).
|
||||
*/
|
||||
onSetSelectionLineStyle: (lineStyleId: string | undefined) => void;
|
||||
/**
|
||||
* Setzt/entfernt die Vollton-Füllfarbe — wirkt NUR auf Drawing2D (sonst No-op).
|
||||
* `null` entfernt die Füllfarbe (Fläche wieder transparent).
|
||||
|
||||
+14
-3
@@ -3012,13 +3012,24 @@ function renderPrimitive(
|
||||
});
|
||||
const extra: SvgLine[] = p.extraLines.map((line) => ({
|
||||
align: line.align,
|
||||
lineHeight: lineGap,
|
||||
tspans: [{ text: line.text, fontSize: baseFs * 0.8, fill: p.color }],
|
||||
}));
|
||||
const lines = [...docLines, ...extra];
|
||||
if (lines.length === 0) return null;
|
||||
// Block vertikal um den Anker zentrieren.
|
||||
const totalH = baseFs + (lines.length - 1) * lineGap;
|
||||
// Block vertikal um den Anker zentrieren. Absätze können eine eigene
|
||||
// Zeilenhöhe tragen (Text-Gruppe, Zeilenhöhe-Regler) — darum kumulierte
|
||||
// Vorschübe statt eines einzigen festen lineGap.
|
||||
const totalH = baseFs + lines.slice(1).reduce((sum, l) => sum + l.lineHeight, 0);
|
||||
const startY = c.y - totalH / 2 + baseFs * 0.8;
|
||||
const lineYs: number[] = [];
|
||||
{
|
||||
let y = startY;
|
||||
lines.forEach((line, k) => {
|
||||
if (k > 0) y += line.lineHeight;
|
||||
lineYs.push(y);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<text style={{ pointerEvents: "none", userSelect: "none" }}>
|
||||
{lines.map((line, k) => {
|
||||
@@ -3032,7 +3043,7 @@ function renderPrimitive(
|
||||
<tspan
|
||||
key={k}
|
||||
x={c.x}
|
||||
y={startY + k * lineGap}
|
||||
y={lineYs[k]}
|
||||
textAnchor={anchor}
|
||||
>
|
||||
{line.tspans.map((ts, j) => (
|
||||
|
||||
+35
-24
@@ -486,36 +486,47 @@ body {
|
||||
gap: 6px;
|
||||
}
|
||||
/* „+ Text"-Knopf: Pille mit Icon + Label. */
|
||||
.tb-addtext {
|
||||
/* Zeilenhöhe-Regler (ersetzt den früheren „+ Text"-Knopf — Text wird ein
|
||||
eigenständiges Zeichenwerkzeug, AUDIT B1). Gleiche dunkle Pillen-Optik wie
|
||||
die übrigen Oberleisten-Controls (.tb-dd-trigger/.tb-seg). */
|
||||
.tb-lineheight {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
height: 24px;
|
||||
width: 80px;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #4a4a4a;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #2c2c2c;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.tb-lineheight-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 80px;
|
||||
height: 24px;
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
background: var(--input);
|
||||
color: var(--ink-2);
|
||||
font-family: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
width: 22px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: #b0b0b0;
|
||||
cursor: pointer;
|
||||
transition: background 0.14s, color 0.14s, border-color 0.14s;
|
||||
transition: background 0.14s, color 0.14s;
|
||||
}
|
||||
.tb-addtext:hover:not(:disabled) {
|
||||
background: var(--accent-dim);
|
||||
border-color: var(--accent-border);
|
||||
color: var(--accent);
|
||||
.tb-lineheight-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #f0f0f0;
|
||||
}
|
||||
.tb-addtext:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
.tb-addtext-label {
|
||||
.tb-lineheight-value {
|
||||
flex: 1 1 auto;
|
||||
text-align: left;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
color: #e8e8e8;
|
||||
border-left: 1px solid #4a4a4a;
|
||||
border-right: 1px solid #4a4a4a;
|
||||
}
|
||||
|
||||
/* Marken-Gruppe: Wortmarke + kleiner Icon-Stapel daneben (Settings/Ressourcen),
|
||||
|
||||
+18
-6
@@ -85,10 +85,13 @@ export function docToHtml(doc: RichTextDoc, opts: HtmlOptions = {}): string {
|
||||
}
|
||||
|
||||
function paragraphToHtml(p: Paragraph, basePt: number, color?: string): string {
|
||||
const align = p.align && p.align !== "left" ? ` style="text-align:${p.align}"` : "";
|
||||
if (p.runs.length === 0) return `<div${align}><br></div>`;
|
||||
const styles: string[] = [];
|
||||
if (p.align && p.align !== "left") styles.push(`text-align:${p.align}`);
|
||||
if (p.lineHeight != null) styles.push(`line-height:${p.lineHeight}`);
|
||||
const style = styles.length ? ` style="${styles.join(";")}"` : "";
|
||||
if (p.runs.length === 0) return `<div${style}><br></div>`;
|
||||
const inner = p.runs.map((r) => runToHtmlSpan(r, basePt, color)).join("");
|
||||
return `<div${align}>${inner}</div>`;
|
||||
return `<div${style}>${inner}</div>`;
|
||||
}
|
||||
|
||||
function runToHtmlSpan(run: TextRun, basePt: number, color?: string): string {
|
||||
@@ -119,6 +122,8 @@ export interface SvgTspan {
|
||||
export interface SvgLine {
|
||||
tspans: SvgTspan[];
|
||||
align: "left" | "center" | "right";
|
||||
/** Zeilenhöhe dieses Absatzes (Vielfaches der Basisgrösse), s. `Paragraph.lineHeight`. */
|
||||
lineHeight: number;
|
||||
}
|
||||
|
||||
export interface SvgTextOptions {
|
||||
@@ -126,7 +131,12 @@ export interface SvgTextOptions {
|
||||
x: number;
|
||||
/** Baseline der ERSTEN Zeile, Y in Benutzereinheiten. */
|
||||
y: number;
|
||||
/** Zeilenvorschub (Baseline zu Baseline) in Benutzereinheiten. */
|
||||
/**
|
||||
* Zeilenvorschub (Baseline zu Baseline) in Benutzereinheiten für Zeilen OHNE
|
||||
* eigene `lineHeight` (Rückwärts-Kompatibilität/Fallback). Absätze mit
|
||||
* gesetzter `Paragraph.lineHeight` berechnen ihren Vorschub stattdessen
|
||||
* selbst aus `basePt · unitPerPt · lineHeight`.
|
||||
*/
|
||||
lineHeight: number;
|
||||
/**
|
||||
* Umrechnung Schriftgrösse: `sizePt` × `unitPerPt` = Grösse in Benutzereinheiten.
|
||||
@@ -142,13 +152,15 @@ export interface SvgTextOptions {
|
||||
/**
|
||||
* Zerlegt das Dokument in Zeilen/Tspans mit bereits in Benutzereinheiten
|
||||
* umgerechneten Grössen. Der Aufrufer positioniert die Zeilen selbst (Baseline
|
||||
* der Zeile i = `y + i·lineHeight`).
|
||||
* der Zeile i = `y + Σ lineHeight[0..i-1]`, s. `docToSvgText`).
|
||||
*/
|
||||
export function docToLines(doc: RichTextDoc, opts: SvgTextOptions): SvgLine[] {
|
||||
const basePt = opts.basePt ?? 12;
|
||||
const color = opts.color ?? "#000000";
|
||||
const baseUnit = basePt * opts.unitPerPt;
|
||||
return doc.paragraphs.map((p) => ({
|
||||
align: p.align ?? "left",
|
||||
lineHeight: p.lineHeight != null ? baseUnit * p.lineHeight : opts.lineHeight,
|
||||
tspans: p.runs
|
||||
.filter((r) => r.text.length > 0)
|
||||
.map((r) => runToTspan(r, basePt, opts.unitPerPt, color)),
|
||||
@@ -184,7 +196,7 @@ export function docToSvgText(doc: RichTextDoc, opts: SvgTextOptions): string {
|
||||
const lines = docToLines(doc, opts);
|
||||
const parts: string[] = [];
|
||||
lines.forEach((line, i) => {
|
||||
const dy = i === 0 ? 0 : opts.lineHeight;
|
||||
const dy = i === 0 ? 0 : line.lineHeight;
|
||||
const anchor = line.align === "center" ? "middle" : line.align === "right" ? "end" : "start";
|
||||
const runSpans = line.tspans.map((t) => tspanToSvg(t)).join("");
|
||||
// Leere Zeile: ein Zero-Width-Space, damit dy dennoch wirkt.
|
||||
|
||||
@@ -44,10 +44,15 @@ export interface TextRun {
|
||||
/** Absatz-Ausrichtung. */
|
||||
export type Align = "left" | "center" | "right";
|
||||
|
||||
/** Ein Absatz: eine Zeile aus Runs plus optionale Ausrichtung. */
|
||||
/** Zeilenhöhe als Vielfaches der Basisgrösse, wenn nicht am Absatz gesetzt. */
|
||||
export const DEFAULT_LINE_HEIGHT = 1.3;
|
||||
|
||||
/** Ein Absatz: eine Zeile aus Runs plus optionale Ausrichtung/Zeilenhöhe. */
|
||||
export interface Paragraph {
|
||||
runs: TextRun[];
|
||||
align?: Align;
|
||||
/** Zeilenhöhe als Vielfaches der Basisgrösse (z. B. 1.3 = 130 %). */
|
||||
lineHeight?: number;
|
||||
}
|
||||
|
||||
/** Das gesamte Rich-Text-Dokument. */
|
||||
|
||||
+85
-23
@@ -19,6 +19,7 @@ import {
|
||||
applyMark,
|
||||
applyPreset,
|
||||
commonMarkValue,
|
||||
DEFAULT_LINE_HEIGHT,
|
||||
DEFAULT_PRESETS,
|
||||
docLength,
|
||||
isMarkActive,
|
||||
@@ -172,11 +173,6 @@ export interface TopBarProps {
|
||||
* Setter. Ist `range` `null`, wirken Aktionen auf das GANZE Doc.
|
||||
*/
|
||||
textTarget: TextTarget | null;
|
||||
/**
|
||||
* Startet das Text-Werkzeug (neues Textobjekt platzieren). `null`, solange das
|
||||
* Werkzeug noch nicht existiert → der „+ Text"-Knopf ist dann deaktiviert.
|
||||
*/
|
||||
onAddText: (() => void) | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -556,16 +552,15 @@ const TEXT_SIZES: number[] = [8, 9, 10, 11, 12, 14, 18, 24, 36, 48];
|
||||
*/
|
||||
function TextGroup({
|
||||
textTarget,
|
||||
onAddText,
|
||||
}: {
|
||||
textTarget: TextTarget | null;
|
||||
onAddText: (() => void) | null;
|
||||
}) {
|
||||
// Defaults für neuen Text, solange nichts Text-artiges selektiert ist. Bei
|
||||
// aktivem Ziel spiegeln die Controls die gemeinsamen Werte des Ziels wider.
|
||||
const [defFont, setDefFont] = useState<string>("Inter");
|
||||
const [defSize, setDefSize] = useState<number>(12);
|
||||
const [defAlign, setDefAlign] = useState<Align>("left");
|
||||
const [defLineHeight, setDefLineHeight] = useState<number>(DEFAULT_LINE_HEIGHT);
|
||||
const [defBold, setDefBold] = useState(false);
|
||||
const [defItalic, setDefItalic] = useState(false);
|
||||
const [defUnderline, setDefUnderline] = useState(false);
|
||||
@@ -593,6 +588,10 @@ function TextGroup({
|
||||
const curAlign: Align = textTarget
|
||||
? commonAlign(textTarget.doc, rng) ?? "left"
|
||||
: defAlign;
|
||||
// Zeilenhöhe: gemeinsamer Wert der berührten Absätze, sonst Default.
|
||||
const curLineHeight: number = textTarget
|
||||
? commonLineHeight(textTarget.doc, rng) ?? DEFAULT_LINE_HEIGHT
|
||||
: defLineHeight;
|
||||
|
||||
// ── Aktions-Helfer (wirken auf das Ziel oder setzen Defaults) ──────────────
|
||||
const toggle = (mark: BoolMark, setDefault: (v: boolean) => void, cur: boolean) => {
|
||||
@@ -631,6 +630,15 @@ function TextGroup({
|
||||
}
|
||||
};
|
||||
|
||||
const setLineHeightValue = (lh: number) => {
|
||||
const clamped = Math.round(Math.min(3, Math.max(0.8, lh)) * 10) / 10;
|
||||
if (textTarget) {
|
||||
textTarget.apply(applyLineHeight(textTarget.doc, textTarget.range, clamped));
|
||||
} else {
|
||||
setDefLineHeight(clamped);
|
||||
}
|
||||
};
|
||||
|
||||
const applyPresetValue = (id: string) => {
|
||||
const preset = DEFAULT_PRESETS.find((p) => p.id === id);
|
||||
if (!preset) return;
|
||||
@@ -773,19 +781,36 @@ function TextGroup({
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className="tb-addtext"
|
||||
disabled={onAddText == null}
|
||||
onClick={() => onAddText?.()}
|
||||
title={onAddText ? t("text.add") : t("text.add.hint")}
|
||||
aria-label={t("text.add")}
|
||||
<div
|
||||
className="tb-lineheight"
|
||||
role="group"
|
||||
aria-label={t("text.lineHeight")}
|
||||
title={t("text.lineHeight.hint")}
|
||||
>
|
||||
<span className="material-symbols-outlined tb-ico" aria-hidden="true">
|
||||
add
|
||||
</span>
|
||||
<span className="tb-addtext-label">{t("text.add")}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="tb-lineheight-btn"
|
||||
onClick={() => setLineHeightValue(curLineHeight - 0.1)}
|
||||
title={t("text.lineHeight.decrease")}
|
||||
aria-label={t("text.lineHeight.decrease")}
|
||||
>
|
||||
<span className="material-symbols-outlined tb-ico" aria-hidden="true">
|
||||
remove
|
||||
</span>
|
||||
</button>
|
||||
<span className="tb-lineheight-value">{curLineHeight.toFixed(1)}×</span>
|
||||
<button
|
||||
type="button"
|
||||
className="tb-lineheight-btn"
|
||||
onClick={() => setLineHeightValue(curLineHeight + 0.1)}
|
||||
title={t("text.lineHeight.increase")}
|
||||
aria-label={t("text.lineHeight.increase")}
|
||||
>
|
||||
<span className="material-symbols-outlined tb-ico" aria-hidden="true">
|
||||
add
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -828,6 +853,42 @@ function applyAlign(doc: RichTextDoc, range: TextRange | null, align: Align): Ri
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gemeinsame Zeilenhöhe der vom Bereich berührten Absätze (Vielfaches der
|
||||
* Basisgrösse); `undefined` bei uneinheitlicher Auswahl.
|
||||
*/
|
||||
function commonLineHeight(doc: RichTextDoc, range: TextRange | null): number | undefined {
|
||||
const paras = touchedParagraphs(doc, range);
|
||||
if (paras.length === 0) return doc.paragraphs[0]?.lineHeight ?? DEFAULT_LINE_HEIGHT;
|
||||
let value: number | undefined;
|
||||
let first = true;
|
||||
for (const idx of paras) {
|
||||
const lh = doc.paragraphs[idx]?.lineHeight ?? DEFAULT_LINE_HEIGHT;
|
||||
if (first) {
|
||||
value = lh;
|
||||
first = false;
|
||||
} else if (lh !== value) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Zeilenhöhe auf alle vom Bereich berührten Absätze. Ohne Bereich
|
||||
* (null) auf ALLE Absätze des Dokuments.
|
||||
*/
|
||||
function applyLineHeight(doc: RichTextDoc, range: TextRange | null, lineHeight: number): RichTextDoc {
|
||||
const touched = new Set(touchedParagraphs(doc, range));
|
||||
const all = range == null;
|
||||
return {
|
||||
version: 1,
|
||||
paragraphs: doc.paragraphs.map((p, i) =>
|
||||
all || touched.has(i) ? { ...p, lineHeight } : p,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/** Indizes der Absätze, die der Bereich [start,end) berührt. */
|
||||
function touchedParagraphs(doc: RichTextDoc, range: TextRange | null): number[] {
|
||||
if (range == null || range.start === range.end) {
|
||||
@@ -928,7 +989,6 @@ export function TopBar({
|
||||
onSaveProject,
|
||||
onOpenProject,
|
||||
textTarget,
|
||||
onAddText,
|
||||
}: TopBarProps) {
|
||||
// Dezente Scrollleiste: die Oberleiste scrollt horizontal (overflow-x). Statt
|
||||
// der prägnanten Standard-Leiste blenden wir eine eigene, sehr ruhige Leiste
|
||||
@@ -1105,9 +1165,11 @@ export function TopBar({
|
||||
<Sep />
|
||||
|
||||
{/* Text-Gruppe (immer sichtbar) — Stil/Schrift/Grösse + B/I/U · L/C/R +
|
||||
„+ Text". Formatiert das aktuelle Text-Ziel (Raumstempel) live. Sitzt
|
||||
bewusst zwischen den Ebenen-Kombis und Detailgrad/Massstab. */}
|
||||
<TextGroup textTarget={textTarget} onAddText={onAddText} />
|
||||
Zeilenhöhe. Formatiert das aktuelle Text-Ziel (Raumstempel) live.
|
||||
Sitzt bewusst zwischen den Ebenen-Kombis und Detailgrad/Massstab.
|
||||
„+ Text" ist raus — Text wird ein eigenständiges Zeichenwerkzeug
|
||||
(HANDOVER-Backlog AUDIT B1), kein TopBar-Knopf mehr. */}
|
||||
<TextGroup textTarget={textTarget} />
|
||||
|
||||
<Sep />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user