diff --git a/src/panels/AttributesPanel.tsx b/src/panels/AttributesPanel.tsx index ff71513..97d9867 100644 --- a/src/panels/AttributesPanel.tsx +++ b/src/panels/AttributesPanel.tsx @@ -29,6 +29,7 @@ import { PEN_WEIGHTS } from "../model/types"; import type { AttributeSource } from "../model/types"; import { usePanelHost } from "./host"; import { Dropdown } from "../ui/Dropdown"; +import { ColorHexField } from "../ui/ColorHexField"; /** UI-Zustand des 3-Optionen-Quellen-Dropdowns (nicht 1:1 das Modell-Feld: * „custom" ist abgeleitet aus „ist ein expliziter Wert gesetzt?", nicht @@ -95,11 +96,7 @@ export function AttributesPanel() { // nur, wenn die Quelle „custom" ist — sonst zeigt allein der Dropdown den // Zustand. const colorValue = (value: string | undefined, onSet: (color: string) => void) => ( - + ); // Eine Attribut-Zeile: Label + Quellen-Dropdown + (nur bei „eigener Wert") @@ -170,18 +167,7 @@ export function AttributesPanel() { {/* Farbe — gilt für Wand UND Drawing2D (Kontrakt setzt beide). */} {t("attr.color")} - + {/* Strichstärke (mm) — Wand/Decke/Drawing2D, mit Nach-Ebene/Nach-Bauteil/ diff --git a/src/styles.css b/src/styles.css index 62891c1..bb10fbe 100644 --- a/src/styles.css +++ b/src/styles.css @@ -4018,6 +4018,14 @@ body { display: inline-flex; align-items: center; gap: 6px; +} +/* Swatch + natives Farb-Input — EIGENER Klick-Bereich (öffnet die Palette). + Der Hex-Text daneben ist jetzt ein separates (ColorHexField), + damit er unabhängig editierbar ist statt nur die Palette zu öffnen. */ +.attr-color-swatch-wrap { + display: inline-flex; + align-items: center; + gap: 4px; cursor: pointer; } .attr-color-swatch { @@ -4041,6 +4049,26 @@ body { color: var(--muted); font-size: 11px; } +/* Editierbarer Hex-Code: sieht wie reiner Text aus, bis man klickt/fokussiert + — dann Rahmen + helle Schrift als Editier-Signal. */ +.attr-color-hex-input { + width: 6.5ch; + border: 1px solid transparent; + background: transparent; + border-radius: 3px; + padding: 1px 3px; + font-family: var(--font-mono); + cursor: text; +} +.attr-color-hex-input:hover:not(:disabled) { + border-color: var(--border); +} +.attr-color-hex-input:focus { + outline: none; + border-color: var(--accent); + background: var(--input); + color: var(--ink); +} .attr-color input[type="color"]:disabled { opacity: 0.4; cursor: not-allowed; diff --git a/src/ui/ColorHexField.tsx b/src/ui/ColorHexField.tsx new file mode 100644 index 0000000..df90c6b --- /dev/null +++ b/src/ui/ColorHexField.tsx @@ -0,0 +1,64 @@ +// Farbfeld: Swatch (öffnet die native Farbpalette) + separat editierbarer +// Hex-Code (Tippen/Enter übernimmt, Esc verwirft). Ersetzt die frühere reine +// Text-Anzeige des Hex-Werts, die nicht anklickbar/editierbar war. + +import { useEffect, useState } from "react"; + +export function ColorHexField({ + value, + onChange, + disabled, +}: { + value: string; + onChange: (hex: string) => void; + disabled?: boolean; +}) { + const [text, setText] = useState(value.toUpperCase()); + + // Extern geänderter Wert (z. B. Preset-Klick) synchron halten, solange das + // Feld nicht gerade selbst bearbeitet wird (kein Cursor-Sprung beim Tippen). + useEffect(() => setText(value.toUpperCase()), [value]); + + const commit = (raw: string) => { + const trimmed = raw.trim(); + const withHash = trimmed.startsWith("#") ? trimmed : `#${trimmed}`; + if (/^#[0-9a-fA-F]{6}$/.test(withHash)) { + onChange(withHash.toLowerCase()); + setText(withHash.toUpperCase()); + } else { + setText(value.toUpperCase()); // ungültig → letzten gültigen Wert zeigen + } + }; + + return ( + + + setText(e.target.value)} + onBlur={(e) => commit(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + commit(e.currentTarget.value); + e.currentTarget.blur(); + } else if (e.key === "Escape") { + setText(value.toUpperCase()); + e.currentTarget.blur(); + } + }} + /> + + ); +} diff --git a/src/ui/SettingsDialog.tsx b/src/ui/SettingsDialog.tsx index 121c1e0..d9ff785 100644 --- a/src/ui/SettingsDialog.tsx +++ b/src/ui/SettingsDialog.tsx @@ -13,6 +13,7 @@ import { useEffect, useState } from "react"; import { t } from "../i18n"; import { ACCENT_SWATCHES } from "../theme/accents"; import type { Project } from "../model/types"; +import { ColorHexField } from "./ColorHexField"; export interface SettingsDialogProps { marqueeColor: string; @@ -145,11 +146,7 @@ function AccentColorField({
{label} - +
{ACCENT_SWATCHES.map((a) => (