Pruefdurchgang ueber alle 26 Bildschirme - fuenf Fehler behoben, HIG nachgezogen
Gemessen statt geschaut: eine Pruefroutine lief auf jedem Bildschirm und
pruefte Ueberlauf, Tippziele, abgeschnittenen Text, Bilder ohne Quelle und
WCAG-Kontrast fuer jeden Textknoten. Dazu die Live-App mit echten Daten, die
Musterseite fuer Leerzustaende und das Panel zum Abgleich.
1. Deutsche Datumsangaben wurden falsch gelesen. new Date("01.03.2025")
liest amerikanisch: Tage bis 12 wurden still vertauscht, ab Tag 13 fiel
der Wert ganz weg. Die Erstzulassung stand auf zwei Bildschirmen
unterschiedlich da. alsZeitpunkt() erkennt jetzt ISO, TT.MM.JJJJ und
MM/JJJJ, weist unmoegliche Daten ab und fuehrt die Genauigkeit mit -
aus "08/2026" wird "August 2026", kein erfundener Erster.
2. Die Hauptuntersuchung war in beiden Oberflaechen unerreichbar. Das Panel
leitete sie aus der Erstzulassung ab, aber nur bei MM/JJJJ, und benutzte
den eingetragenen Wert gar nicht. Jetzt beide: eingetragene Faelligkeit
vor Servicebuch vor Erstzulassung + 24 Monate.
3. Die Tankstellenmarke stand zweimal da, wenn der Name kein Komma hat
("Shell München Ost"). ohneMarkeVorn() nimmt sie vorn ab.
4. Bilder wurden geholt, obwohl beide wussten, dass es sie nicht gibt -
fuenf 404 fuer dieselbe Datei in einem Ladevorgang. Jetzt gar keine
Adresse bei Stand 0; nachgemessen 4 Anfragen, alle 200.
5. Ein Bildelement ohne Quelle zeigte das kaputte Bildsymbol, und onerror
greift dort nicht: ohne src gibt es keinen Ladeversuch.
Dazu: leere Klammern beim Steuersatz, leere graue Kachel bei leerer
Leistungsgruppe, Umbruch auf dem Einrichtungsbildschirm, drei
Leerdarstellungen in einer Kachel, fehlende Grundschrift.
Apple HIG: Kontrast war an einer Stelle 3,18 statt 4,5, weisse Schrift auf
der Loeschflaeche 3,41/3,55. Rot als Schrift und Rot als Flaeche sind jetzt
getrennte Token (--bad / --bad-flaeche), beide Apples systemRed fuer
erhoehten Kontrast. Tippziele: unsichtbares 44x44-Overlay, wo die sichtbare
Groesse Teil des Bildes ist, echte Mindesthoehe, wo das Element Flaeche ist -
Formulare werden dadurch sichtbar hoeher.
Neu auf Wunsch: Wischen in der Bildergalerie (beide Richtungen) und die
HIG-konforme Loeschgeste - ein voller Wisch loescht ohne zweiten Tipp, ab
55 % der Zeilenbreite, mit wachsender roter Flaeche und erhaltener
Rueckfrage. Dabei fiel auf, dass der Zugwert aus dem React-Zustand gelesen
wurde und ein schneller Wisch dadurch verlorenging; er liegt jetzt in einer
Referenz.
Paritaet: alle 26 Routen und Titel decken sich, Fahrzeugstatus zeilengleich,
zwei ungeplante Abweichungen (Datumsformatierung, Herkunft der
Hauptuntersuchung) geschlossen.
tsc sauber, 271 Tests, vite build sauber, Design-System gebaut, Panel als
Modul geparst, audi_ha_test auf 2026.9.4.23 ohne Traceback. Live nachgemessen:
Datum auf beiden Bildschirmen gleich, keine 404 mehr, Zurueck-Pfeil sichtbar
34x34 und treffbar 44x44, Galerie wischt in beide Richtungen, voller Wisch
loest die Rueckfrage aus, keine Kontrast-Unterschreitung mehr.
This commit is contained in:
@@ -11,6 +11,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"
|
||||
import {
|
||||
kartendienstUrl,
|
||||
markeErkennen,
|
||||
ohneMarkeVorn,
|
||||
tankstelleKandidaten,
|
||||
tankstelleSuchtext,
|
||||
tankstelleKurz,
|
||||
@@ -306,3 +307,32 @@ describe("gespeicherte Marke (station_brand)", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Marke am Anfang eines Namensteils", () => {
|
||||
/* Am 04.09.2026 an der Musterseite aufgefallen: ein von Hand eingetragener
|
||||
Name ohne Komma rutschte komplett als "Ort" durch. */
|
||||
|
||||
it("laesst die Marke nicht zweimal erscheinen", () => {
|
||||
expect(tankstelleKurz("Shell München Ost", "Musterstraße 5")).toBe(
|
||||
"Shell, Musterstr., München Ost",
|
||||
)
|
||||
})
|
||||
|
||||
it("nimmt der Zeile die Marke ab, laesst den Ort stehen", () => {
|
||||
expect(ohneMarkeVorn("Shell München Ost")).toBe("München Ost")
|
||||
expect(ohneMarkeVorn("Shell, SÖLDEN")).toBe("SÖLDEN")
|
||||
expect(ohneMarkeVorn("Shell")).toBe("")
|
||||
})
|
||||
|
||||
it("laesst einen Teil in Ruhe, in dem die Marke nicht vorn steht", () => {
|
||||
// Dort ist nicht sicher, was Marke und was Ortsname ist.
|
||||
expect(ohneMarkeVorn("Autohof Shell Nord")).toBe("Autohof Shell Nord")
|
||||
expect(ohneMarkeVorn("Musterstraße 5")).toBe("Musterstraße 5")
|
||||
})
|
||||
|
||||
it("achtet auf die Wortgrenze", () => {
|
||||
// "Star" darf nicht in "Starnberg" treffen.
|
||||
expect(ohneMarkeVorn("Starnberg")).toBe("Starnberg")
|
||||
expect(ohneMarkeVorn("Sprintstraße 5")).toBe("Sprintstraße 5")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -69,6 +69,30 @@ const MARKEN = [
|
||||
* geraten wird nur, wo es fehlt: bei Vorgaengen ohne Beleg, bei von Hand
|
||||
* eingetragenen Tankstellen und solange das Nachtragen nicht gelaufen ist.
|
||||
*/
|
||||
/**
|
||||
* Nimmt einem Namensteil die fuehrende Kopfmarke ab.
|
||||
*
|
||||
* WARUM NICHT NUR VERGLEICHEN: bis zum 04.09.2026 wurde ein Namensteil nur
|
||||
* dann uebersprungen, wenn er FUER SICH eine Marke ist ("Shell"). Ein von
|
||||
* Hand eingetragener Name wie "Shell München Ost" hat aber kein Komma - er
|
||||
* rutschte komplett durch und landete als ORT in der Zeile:
|
||||
*
|
||||
* "Shell München Ost" + "Musterstraße 5"
|
||||
* -> "Shell, Musterstr., Shell München Ost"
|
||||
*
|
||||
* Jetzt faellt die Marke vorn ab und der Rest bleibt der Ort. Steht die Marke
|
||||
* NICHT am Anfang ("Autohof Shell Nord"), bleibt der Teil unangetastet -
|
||||
* dort ist nicht sicher, was Marke und was Ortsname ist.
|
||||
*/
|
||||
export function ohneMarkeVorn(teil: string): string {
|
||||
const marke = markeErkennen(teil)
|
||||
if (!marke) return teil
|
||||
// Kein Maskieren noetig: `marke` stammt immer aus MARKEN, und dort steht
|
||||
// kein einziges Sonderzeichen der regulaeren Ausdruecke.
|
||||
const muster = new RegExp(`^${marke}\\b[\\s,]*`, "i")
|
||||
return muster.test(teil) ? teil.replace(muster, "").trim() : teil
|
||||
}
|
||||
|
||||
export function markeErkennen(name: string | null | undefined): string | null {
|
||||
const roh = (name ?? "").trim()
|
||||
if (!roh) return null
|
||||
@@ -162,13 +186,10 @@ export function tankstelleTeile(
|
||||
const quellen: string[] = []
|
||||
for (const stueck of (adresse ?? "").split(",")) quellen.push(stueck)
|
||||
for (const stueck of (name ?? "").split(",")) {
|
||||
const t = stueck.trim()
|
||||
// Ein Teil, der fuer sich genommen eine Kopfmarke IST, taugt nie als Ort.
|
||||
// Frueher stand hier nur der Vergleich mit der gewaehlten Marke; seit die
|
||||
// gespeicherte Angabe vorgeht, koennen beide auseinanderfallen, und dann
|
||||
// rutschte die Marke aus dem Namen in den Ort.
|
||||
if (t && markeErkennen(t)?.toLowerCase() === t.toLowerCase()) continue
|
||||
quellen.push(t)
|
||||
// Die Marke faellt vorn ab - sie steht schon fest und taugt nie als Ort.
|
||||
// Bleibt nichts uebrig, war der Teil nur die Marke selbst.
|
||||
const t = ohneMarkeVorn(stueck.trim())
|
||||
if (t) quellen.push(t)
|
||||
}
|
||||
|
||||
for (const stueck of quellen) {
|
||||
|
||||
@@ -2,7 +2,20 @@ import { describe, expect, it } from "vitest"
|
||||
|
||||
import { alsCsv } from "./screens/csv"
|
||||
import { kalenderDatei } from "./screens/kalender"
|
||||
import { dauer, de, deOderStrich, eur, isoTag, schnittpreis, summe } from "./format"
|
||||
import {
|
||||
datum,
|
||||
datumLang,
|
||||
datumZeit,
|
||||
dauer,
|
||||
de,
|
||||
deOderStrich,
|
||||
eur,
|
||||
isoTag,
|
||||
monatJahr,
|
||||
schnittpreis,
|
||||
summe,
|
||||
uhrzeit,
|
||||
} from "./format"
|
||||
|
||||
describe("Zahlen im deutschen Format", () => {
|
||||
it("setzt Punkt als Tausender- und Komma als Dezimaltrennzeichen", () => {
|
||||
@@ -101,3 +114,67 @@ describe("Kalenderdatei", () => {
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Datumsangaben aus dem Bestand", () => {
|
||||
/* Alle Faelle am 04.09.2026 an der laufenden Instanz gemessen - vorher
|
||||
vertauschte `new Date()` Tag und Monat, oder verwarf den Wert ganz. */
|
||||
|
||||
it("liest deutsche Tagesdaten richtig herum", () => {
|
||||
// Der gemeldete Fall: die Erstzulassung stand als 03.01.2025 da.
|
||||
expect(datum("01.03.2025")).toBe("01.03.2025")
|
||||
expect(datum("12.11.2024")).toBe("12.11.2024")
|
||||
expect(datum("1.3.2025")).toBe("01.03.2025")
|
||||
})
|
||||
|
||||
it("verliert Tage ab dem 13. nicht mehr", () => {
|
||||
// Die waren vorher ungueltig und verschwanden als "–".
|
||||
expect(datum("25.03.2025")).toBe("25.03.2025")
|
||||
expect(datum("31.12.2023")).toBe("31.12.2023")
|
||||
})
|
||||
|
||||
it("zeigt monatsgenaue Angaben als Monat, nicht als erfundenen Tag", () => {
|
||||
// Die Hauptuntersuchung steht als "MM/JJJJ" im Profil (so verlangt es das
|
||||
// Einrichtungsfeld). Vorher: "–". Ein "01.08.2026" waere eine Erfindung.
|
||||
expect(datum("08/2026")).toBe("August 2026")
|
||||
expect(datum("08.2026")).toBe("August 2026")
|
||||
expect(datum("2026-08")).toBe("August 2026")
|
||||
expect(datumLang("08/2026")).toBe("August 2026")
|
||||
})
|
||||
|
||||
it("gibt einer monatsgenauen Angabe keine Uhrzeit", () => {
|
||||
// 00:00 waere erfunden.
|
||||
expect(uhrzeit("08/2026")).toBe("–")
|
||||
expect(datumZeit("08/2026")).toBe("August 2026")
|
||||
})
|
||||
|
||||
it("laesst ISO vom Backend unveraendert richtig", () => {
|
||||
expect(datum("2026-08-14")).toBe("14.08.2026")
|
||||
expect(datum("2024-09-25T15:47:00")).toBe("25.09.2024")
|
||||
expect(datumZeit("2024-09-25T15:47:00")).toBe("25.09.2024, 15:47 Uhr")
|
||||
})
|
||||
|
||||
it("weist unmoegliche Daten ab, statt still ein anderes zu zeigen", () => {
|
||||
// new Date(2025,1,31) waere in JavaScript der 3. Maerz.
|
||||
expect(datum("31.02.2025")).toBe("–")
|
||||
expect(datum("32.01.2025")).toBe("–")
|
||||
expect(datum("00.01.2025")).toBe("–")
|
||||
expect(datum("01.13.2025")).toBe("–")
|
||||
})
|
||||
|
||||
it("bleibt bei Unbekanntem beim Strich", () => {
|
||||
expect(datum(null)).toBe("–")
|
||||
expect(datum("")).toBe("–")
|
||||
expect(datum("demnaechst")).toBe("–")
|
||||
expect(datum("01/03/2025")).toBe("–")
|
||||
})
|
||||
|
||||
it("nimmt Date und Zahl weiterhin an", () => {
|
||||
expect(datum(new Date(2025, 2, 1))).toBe("01.03.2025")
|
||||
expect(datum(new Date(2025, 2, 1).getTime())).toBe("01.03.2025")
|
||||
})
|
||||
|
||||
it("monatJahr kann beide Genauigkeiten", () => {
|
||||
expect(monatJahr("2026-08-14")).toBe("August 2026")
|
||||
expect(monatJahr("08/2026")).toBe("August 2026")
|
||||
})
|
||||
})
|
||||
|
||||
+115
-13
@@ -88,35 +88,137 @@ const UHRZEIT = new Intl.DateTimeFormat("de-DE", { hour: "2-digit", minute: "2-d
|
||||
|
||||
const MONAT_JAHR = new Intl.DateTimeFormat("de-DE", { month: "long", year: "numeric" })
|
||||
|
||||
function alsDatum(wert: string | number | Date | null | undefined): Date | null {
|
||||
/**
|
||||
* Datumsangaben aus dem Bestand kommen in DREI Formen - deshalb reicht
|
||||
* `new Date(wert)` hier nicht.
|
||||
*
|
||||
* WAS AM 04.09.2026 GEMESSEN WURDE
|
||||
* --------------------------------
|
||||
* `new Date()` liest einen nicht-ISO-Text nach amerikanischer Lesart, also
|
||||
* Monat vor Tag. Fuer deutsche Eingaben heisst das:
|
||||
*
|
||||
* "01.03.2025" (1. Maerz) -> 3. Januar 2025 - Tag und Monat vertauscht
|
||||
* "12.11.2024" -> 11. Dezember - dito
|
||||
* "25.03.2025" -> ungueltig - Wert verschwand als "-"
|
||||
* "08/2026" (Hauptunters.) -> ungueltig - dito
|
||||
*
|
||||
* Tage bis 12 wurden also STILL vertauscht, ab Tag 13 fiel der Wert ganz weg.
|
||||
* Sichtbar war es an der Erstzulassung: "Mein Audi" zeigte den Rohwert
|
||||
* richtig, "Identitaet und Technik" formatierte ihn und drehte ihn dabei um -
|
||||
* derselbe Wert, zwei Ergebnisse. Und die Hauptuntersuchung stand als "-" da,
|
||||
* obwohl "08/2026" gespeichert war.
|
||||
*
|
||||
* WARUM EIGENE MUSTER STATT EINER BIBLIOTHEK
|
||||
* ------------------------------------------
|
||||
* Es sind genau drei Formen, alle aus diesem Projekt: ISO vom Backend,
|
||||
* "TT.MM.JJJJ" aus den Eingabefeldern, "MM/JJJJ" fuer die Hauptuntersuchung
|
||||
* (so verlangt es das Einrichtungsfeld, siehe Panel `einErstzulassung`).
|
||||
* Erkannt wird nur, was hier wirklich vorkommt - alles andere bleibt `null`
|
||||
* und damit "-". Lieber kein Datum als ein erfundenes.
|
||||
*
|
||||
* MONATSGENAU IST EINE EIGENE GENAUIGKEIT
|
||||
* ---------------------------------------
|
||||
* "08/2026" nennt keinen Tag. Daraus den 1. August zu machen und
|
||||
* "01.08.2026" anzuzeigen waere eine Erfindung - deshalb traegt das Ergebnis
|
||||
* seine Genauigkeit mit sich, und `datum()` zeigt dann "August 2026".
|
||||
*/
|
||||
type Genauigkeit = "tag" | "monat"
|
||||
interface Zeitpunkt {
|
||||
zeit: Date
|
||||
genau: Genauigkeit
|
||||
}
|
||||
|
||||
/* ISO vom Backend, mit oder ohne Uhrzeit - das kann `new Date` selbst, und
|
||||
zwar nach Norm statt nach Gutduenken. */
|
||||
const ISO = /^\d{4}-\d{2}-\d{2}([T ]|$)/
|
||||
/* "1.3.2025" wie "01.03.2025". */
|
||||
const DE_TAG = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/
|
||||
/* "08/2026" und "08.2026" - Monat und Jahr, ohne Tag. */
|
||||
const DE_MONAT = /^(\d{1,2})[./](\d{4})$/
|
||||
/* "2026-08" - dieselbe Genauigkeit, in ISO-Schreibweise. */
|
||||
const ISO_MONAT = /^(\d{4})-(\d{1,2})$/
|
||||
|
||||
/**
|
||||
* Baut das Datum und weist die Rollover-Falle ab: `new Date(2025, 1, 31)` ist
|
||||
* in JavaScript der 3. Maerz, nicht der 31. Februar. Ein unmoegliches Datum
|
||||
* soll "-" ergeben, nicht klammheimlich ein anderes.
|
||||
*/
|
||||
function bauen(jahr: number, monat: number, tag: number): Date | null {
|
||||
if (monat < 1 || monat > 12 || tag < 1 || tag > 31) return null
|
||||
const d = new Date(jahr, monat - 1, tag)
|
||||
if (d.getFullYear() !== jahr || d.getMonth() !== monat - 1 || d.getDate() !== tag) return null
|
||||
return d
|
||||
}
|
||||
|
||||
function alsZeitpunkt(wert: string | number | Date | null | undefined): Zeitpunkt | null {
|
||||
if (wert === null || wert === undefined || wert === "") return null
|
||||
const datum = wert instanceof Date ? wert : new Date(wert)
|
||||
return Number.isNaN(datum.getTime()) ? null : datum
|
||||
if (wert instanceof Date) return Number.isNaN(wert.getTime()) ? null : { zeit: wert, genau: "tag" }
|
||||
if (typeof wert === "number") {
|
||||
const d = new Date(wert)
|
||||
return Number.isNaN(d.getTime()) ? null : { zeit: d, genau: "tag" }
|
||||
}
|
||||
|
||||
const text = wert.trim()
|
||||
if (!text) return null
|
||||
|
||||
if (ISO.test(text)) {
|
||||
const d = new Date(text)
|
||||
return Number.isNaN(d.getTime()) ? null : { zeit: d, genau: "tag" }
|
||||
}
|
||||
|
||||
const tag = DE_TAG.exec(text)
|
||||
if (tag) {
|
||||
const d = bauen(Number(tag[3]), Number(tag[2]), Number(tag[1]))
|
||||
return d ? { zeit: d, genau: "tag" } : null
|
||||
}
|
||||
|
||||
const monat = DE_MONAT.exec(text)
|
||||
if (monat) {
|
||||
const d = bauen(Number(monat[2]), Number(monat[1]), 1)
|
||||
return d ? { zeit: d, genau: "monat" } : null
|
||||
}
|
||||
|
||||
const isoMonat = ISO_MONAT.exec(text)
|
||||
if (isoMonat) {
|
||||
const d = bauen(Number(isoMonat[1]), Number(isoMonat[2]), 1)
|
||||
return d ? { zeit: d, genau: "monat" } : null
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function alsDatum(wert: string | number | Date | null | undefined): Date | null {
|
||||
return alsZeitpunkt(wert)?.zeit ?? null
|
||||
}
|
||||
|
||||
export function datum(wert: string | number | Date | null | undefined): string {
|
||||
const d = alsDatum(wert)
|
||||
return d ? DATUM_KURZ.format(d) : "–"
|
||||
const z = alsZeitpunkt(wert)
|
||||
if (!z) return "–"
|
||||
return z.genau === "monat" ? MONAT_JAHR.format(z.zeit) : DATUM_KURZ.format(z.zeit)
|
||||
}
|
||||
|
||||
export function datumLang(wert: string | number | Date | null | undefined): string {
|
||||
const d = alsDatum(wert)
|
||||
return d ? DATUM_LANG.format(d) : "–"
|
||||
const z = alsZeitpunkt(wert)
|
||||
if (!z) return "–"
|
||||
return z.genau === "monat" ? MONAT_JAHR.format(z.zeit) : DATUM_LANG.format(z.zeit)
|
||||
}
|
||||
|
||||
export function uhrzeit(wert: string | number | Date | null | undefined): string {
|
||||
const d = alsDatum(wert)
|
||||
return d ? UHRZEIT.format(d) : "–"
|
||||
const z = alsZeitpunkt(wert)
|
||||
// Eine monatsgenaue Angabe hat keine Uhrzeit - 00:00 waere erfunden.
|
||||
return z && z.genau === "tag" ? UHRZEIT.format(z.zeit) : "–"
|
||||
}
|
||||
|
||||
export function datumZeit(wert: string | number | Date | null | undefined): string {
|
||||
const d = alsDatum(wert)
|
||||
return d ? `${DATUM_KURZ.format(d)}, ${UHRZEIT.format(d)} Uhr` : "–"
|
||||
const z = alsZeitpunkt(wert)
|
||||
if (!z) return "–"
|
||||
if (z.genau === "monat") return MONAT_JAHR.format(z.zeit)
|
||||
return `${DATUM_KURZ.format(z.zeit)}, ${UHRZEIT.format(z.zeit)} Uhr`
|
||||
}
|
||||
|
||||
export function monatJahr(wert: string | number | Date | null | undefined): string {
|
||||
const d = alsDatum(wert)
|
||||
return d ? MONAT_JAHR.format(d) : "–"
|
||||
const z = alsZeitpunkt(wert)
|
||||
return z ? MONAT_JAHR.format(z.zeit) : "–"
|
||||
}
|
||||
|
||||
/** Dauer in Sekunden als „2 h 14 min" bzw. „14 min". */
|
||||
|
||||
@@ -17,9 +17,9 @@ import { OtaFehler, buendelAnwenden, buendelPasst, otaMoeglich } from "../daten/
|
||||
import type { Einstellungen as EinstellungenWerte } from "../daten/profilAdapter"
|
||||
import { datum, datumZeit, de, isoTag, standAlter } from "../format"
|
||||
import { useTheme } from "../theme"
|
||||
import { BildMitMenue, Wertzeile, Werteliste } from "./bausteine"
|
||||
import { BildMitMenue, Markenlogo, Werteliste, Wertzeile } from "./bausteine"
|
||||
import { bestaetigen } from "./bestaetigung"
|
||||
import { BILDPLAETZE, SEITE_WINTER, markenlogoUrl } from "./bilder"
|
||||
import { BILDPLAETZE, SEITE_WINTER } from "./bilder"
|
||||
import { DatensatzPopup } from "./DatensatzPopup"
|
||||
import { HistorienImport } from "./HistorienImport"
|
||||
import type { ServicebuchEintrag } from "./Service"
|
||||
@@ -530,14 +530,7 @@ export function Einstellungen({
|
||||
<Feld
|
||||
label={
|
||||
<span style={{ display: "flex", alignItems: "center", gap: 6 }}>
|
||||
<img
|
||||
src={markenlogoUrl()}
|
||||
alt={werte.markenname}
|
||||
className="dm-markenlogo"
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = "none"
|
||||
}}
|
||||
/>
|
||||
<Markenlogo alt={werte.markenname} className="dm-markenlogo" />
|
||||
{/* Das Panel nennt den Schalter "SmartDeal aktiv" - fest, nicht
|
||||
aus dem Markennamen gebaut. */}
|
||||
SmartDeal aktiv
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
* Werteliste + Chevron statt einer Zeile Fließtext.
|
||||
*/
|
||||
|
||||
import { useState } from "react"
|
||||
import { useRef, useState, type PointerEvent as ReactPointerEvent } from "react"
|
||||
|
||||
import { Tile } from "@audi-dash/ui"
|
||||
|
||||
@@ -58,6 +58,36 @@ export function MeinAudi({ geheZu }: { geheZu: (name: SeitenName, id?: string) =
|
||||
return wert
|
||||
})
|
||||
}
|
||||
/* Wischen in der Bildergalerie (Wunsch des Eigentuemers, 04.09.2026).
|
||||
|
||||
Zeigerereignisse statt Beruehrungsereignisse: dieselbe Verdrahtung
|
||||
traegt Finger, Stift und Maus, und genau so macht es das Standort-
|
||||
Blatt schon (Standort.tsx). Ein Wisch ab 40 px zaehlt als Wisch, alles
|
||||
darunter bleibt ein Tipp - dieselbe Schwelle wie dort.
|
||||
|
||||
Ueberwiegt die senkrechte Bewegung, war es ein Scrollen und kein
|
||||
Blaettern - dann passiert nichts.
|
||||
|
||||
`gewischt` schluckt den anschliessenden Klick: ein Wisch loest auf dem
|
||||
Knopf auch ein click aus, und ohne diese Sperre schaltete die Galerie
|
||||
zweimal weiter. Wortgleich mit galerieWischenVerdrahten() im Panel. */
|
||||
const zug = useRef<{ x0: number; y0: number } | null>(null)
|
||||
const gewischt = useRef(false)
|
||||
const weiter = (richtung: number) =>
|
||||
setzeBildIndex((i) => (i + richtung + GALERIE.length) % GALERIE.length)
|
||||
const zugStart = (e: ReactPointerEvent) => {
|
||||
zug.current = { x0: e.clientX, y0: e.clientY }
|
||||
}
|
||||
const zugEnde = (e: ReactPointerEvent) => {
|
||||
const z = zug.current
|
||||
zug.current = null
|
||||
if (!z) return
|
||||
const dx = e.clientX - z.x0
|
||||
if (Math.abs(dx) <= 40 || Math.abs(e.clientY - z.y0) > Math.abs(dx)) return
|
||||
gewischt.current = true
|
||||
weiter(dx < 0 ? 1 : -1)
|
||||
}
|
||||
|
||||
if (!fahrzeug || !einstellungen) return null
|
||||
|
||||
const aktiverSatz =
|
||||
@@ -128,8 +158,19 @@ export function MeinAudi({ geheZu }: { geheZu: (name: SeitenName, id?: string) =
|
||||
<button
|
||||
type="button"
|
||||
className="dm-galerie-wechsel"
|
||||
onClick={() => setzeBildIndex((i) => (i + 1) % GALERIE.length)}
|
||||
aria-label="Nächstes Bild"
|
||||
onPointerDown={zugStart}
|
||||
onPointerUp={zugEnde}
|
||||
onPointerCancel={() => {
|
||||
zug.current = null
|
||||
}}
|
||||
onClick={() => {
|
||||
if (gewischt.current) {
|
||||
gewischt.current = false
|
||||
return
|
||||
}
|
||||
weiter(1)
|
||||
}}
|
||||
aria-label="Nächstes Bild — oder nach links/rechts wischen"
|
||||
>
|
||||
<Bild datei={platz.datei} beschreibung={platz.beschreibung} name={platz.beschreibung} />
|
||||
</button>
|
||||
@@ -167,9 +208,11 @@ export function MeinAudi({ geheZu }: { geheZu: (name: SeitenName, id?: string) =
|
||||
Hinweis, dass es sie gibt, statt einer abgeschnittenen
|
||||
Motorzeile. */}
|
||||
<Wertzeile label="Details" wert="Informationen über das Auto" hinweis />
|
||||
{/* Leerer Wert statt Strich, wie vAudi(): das Panel gibt hier
|
||||
den Rohwert aus. */}
|
||||
<Wertzeile label="Erstzulassung" wert={fahrzeug.erstzulassung} />
|
||||
{/* Formatiert und mit Strich, wenn nichts da ist - vorher stand
|
||||
hier der Rohwert und bei leerem Feld gar nichts, waehrend
|
||||
FIN daneben "–" und Ausfuehrung "noch offen" zeigte:
|
||||
drei Leerdarstellungen in einer Kachel (04.09.2026). */}
|
||||
<Wertzeile label="Erstzulassung" wert={datum(fahrzeug.erstzulassung)} />
|
||||
<Wertzeile label="Ausführung" wert={ausfuehrung || "noch offen"} />
|
||||
<Wertzeile label="Ausstattung" wert={`${positionen} Positionen`} />
|
||||
</>
|
||||
|
||||
@@ -17,9 +17,8 @@ import { useDaten } from "../daten/DatenKontext"
|
||||
import { kartendienstUrl, tankstelleZeilen } from "../daten/tankstelle"
|
||||
import { datum, de, deOderStrich, eur, uhrzeit } from "../format"
|
||||
import type { SeitenName } from "../navigation"
|
||||
import { KRAFTSTOFFSORTEN, Leerzustand, Wertzeile, Werteliste } from "./bausteine"
|
||||
import { KRAFTSTOFFSORTEN, Leerzustand, Markenlogo, Werteliste, Wertzeile } from "./bausteine"
|
||||
import { LOESCH_HINWEIS, LOESCH_TEXT, bestaetigen } from "./bestaetigung"
|
||||
import { markenlogoUrl } from "./bilder"
|
||||
import { BelegPopup } from "./BelegPopup"
|
||||
import { TankstellenKarte } from "./TankstellenKarte"
|
||||
|
||||
@@ -116,13 +115,7 @@ export function TankDetail({ id, geheZu }: { id: string | undefined; geheZu: (na
|
||||
<Wertzeile
|
||||
label={
|
||||
<span className="dm-marke">
|
||||
<img
|
||||
src={markenlogoUrl()}
|
||||
alt=""
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = "none"
|
||||
}}
|
||||
/>
|
||||
<Markenlogo />
|
||||
SmartDeal-Ersparnis
|
||||
</span>
|
||||
}
|
||||
|
||||
@@ -21,9 +21,8 @@ import {
|
||||
geteiltenBelegSetzen,
|
||||
geteiltenBelegStand,
|
||||
} from "../daten/geteilterBeleg"
|
||||
import { Blattzeile, KRAFTSTOFFSORTEN, Leerzustand } from "./bausteine"
|
||||
import { Blattzeile, KRAFTSTOFFSORTEN, Leerzustand, Markenlogo } from "./bausteine"
|
||||
import { BelegPopup } from "./BelegPopup"
|
||||
import { markenlogoUrl } from "./bilder"
|
||||
import { belegFormularwerte, type BelegEntwurf } from "../daten/belegentwurf"
|
||||
import { tankstelleKurz } from "../daten/tankstelle"
|
||||
import { LOESCH_HINWEIS } from "./bestaetigung"
|
||||
@@ -75,14 +74,10 @@ export function Tanken({ geheZu }: { geheZu: (name: SeitenName, id?: string) =>
|
||||
</span>
|
||||
{smartdealAktiv && (
|
||||
<Pill>
|
||||
<img
|
||||
src={markenlogoUrl()}
|
||||
<Markenlogo
|
||||
alt={einstellungen?.markenname ?? "Logo"}
|
||||
className="dm-markenlogo"
|
||||
style={{ marginRight: 6 }}
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = "none"
|
||||
}}
|
||||
/>
|
||||
−{eur(ersparnisJahr)} €
|
||||
</Pill>
|
||||
|
||||
@@ -39,6 +39,7 @@ export function Versicherung({ geheZu }: { geheZu: (name: SeitenName, id?: strin
|
||||
const v = fahrzeug.versicherung
|
||||
const teile = (v["teile"] ?? []) as Beitragsposten[]
|
||||
const beitrag = (v["beitrag"] as number) ?? 0
|
||||
const steuersatz = ((v["steuersatz"] as string) ?? "").trim()
|
||||
const selbst = (v["selbst"] ?? []) as [string, string][]
|
||||
const service = (v["service"] as string) ?? ""
|
||||
const notfall = (v["notfall"] as string) ?? ""
|
||||
@@ -67,7 +68,9 @@ export function Versicherung({ geheZu }: { geheZu: (name: SeitenName, id?: strin
|
||||
</div>
|
||||
<span className="ads-eyebrow" style={{ marginTop: "14px" }}>
|
||||
{(v["zahlweise"] as string) ?? ""} · enthält {eur((v["steueranteil"] as number) ?? 0)} €
|
||||
Versicherungsteuer ({(v["steuersatz"] as string) ?? ""})
|
||||
{/* Ohne Steuersatz KEINE leeren Klammern - bis zum 04.09.2026
|
||||
stand dort ein nacktes Klammerpaar. */}
|
||||
Versicherungsteuer{steuersatz ? ` (${steuersatz})` : ""}
|
||||
</span>
|
||||
<Werteliste
|
||||
kinder={teile.map((t, i) => (
|
||||
@@ -411,7 +414,12 @@ export function Vertragsdetails({ geheZu }: { geheZu: (name: SeitenName, id?: st
|
||||
if (!fahrzeug) return null
|
||||
|
||||
const v = fahrzeug.versicherung
|
||||
const leistungen = (v["leistungen"] ?? []) as Leistungsgruppe[]
|
||||
/* Gruppen ohne Namen UND ohne Posten fallen raus: sie ergaben eine leere
|
||||
graue Kachel (am 04.09.2026 auf der Musterseite gesehen). Eine Kachel
|
||||
ohne Inhalt sagt nichts und sieht aus wie ein Ladefehler. */
|
||||
const leistungen = ((v["leistungen"] ?? []) as Leistungsgruppe[]).filter(
|
||||
(g) => (g.gruppe ?? "").trim() || (g.posten ?? []).length,
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
* gebraucht wird, aber zu speziell für das Design-System ist.
|
||||
*/
|
||||
|
||||
import { useRef, useState, type ReactNode, type TouchEvent as ReactTouchEvent } from "react"
|
||||
import {
|
||||
useRef,
|
||||
useState,
|
||||
type CSSProperties,
|
||||
type ReactNode,
|
||||
type TouchEvent as ReactTouchEvent,
|
||||
} from "react"
|
||||
|
||||
import { ActionButton, ActionSheet, Tile } from "@audi-dash/ui"
|
||||
|
||||
@@ -11,7 +17,7 @@ import type { SicherheitsPunkt } from "../api"
|
||||
import { useDaten } from "../daten/DatenKontext"
|
||||
import { SymbolZahnradVoll } from "../symbole"
|
||||
import { Bild } from "./Bild"
|
||||
import { bildAlsBase64, bildVerkleinern, bildVersionErneuern } from "./bilder"
|
||||
import { bildAlsBase64, bildVerkleinern, bildVersionErneuern, markenlogoUrl } from "./bilder"
|
||||
import { LOESCH_HINWEIS, LOESCH_TEXT, bestaetigen } from "./bestaetigung"
|
||||
|
||||
/** Kraftstoffsorten zur Auswahl - dieselbe feste Liste wie `KRAFTSTOFFSORTEN`
|
||||
@@ -350,6 +356,16 @@ export function StatusKreis({ ok }: { ok: boolean | null }) {
|
||||
*/
|
||||
const WISCH_BREITE = 88
|
||||
const WISCH_START = 10
|
||||
/* Ab diesem Anteil der Zeilenbreite loescht das Loslassen sofort, ohne
|
||||
zweiten Tipp - so macht es iOS, und genau das fehlte hier: ein voller
|
||||
Wisch blieb wie ein kurzer an der Raste stehen.
|
||||
|
||||
0,55 haelt die Geste absichtsvoll. Eine halbe Zeile ist beim seitlichen
|
||||
Scrollen leicht versehentlich erreicht, eine klare Mehrheit nicht.
|
||||
Wortgleich mit FULL_SWIPE_FRACTION in SwipeRow (Design-System). */
|
||||
const WISCH_VOLL_ANTEIL = 0.55
|
||||
const WISCH_VOLL_MAX = 220
|
||||
const WISCH_VOLL_MIN = 120
|
||||
|
||||
export function WischLoeschen({
|
||||
beiLoeschen,
|
||||
@@ -362,11 +378,27 @@ export function WischLoeschen({
|
||||
children: ReactNode
|
||||
}) {
|
||||
const [offen, setzeOffen] = useState(false)
|
||||
const [zug, setzeZug] = useState<number | null>(null)
|
||||
/* Der Zug liegt zusaetzlich in einer Referenz, und die entscheidet.
|
||||
Der Zustand hinkt eine Darstellung hinterher: treffen die letzte
|
||||
Bewegung und das Loslassen in derselben Aufgabe ein - genau das tut
|
||||
ein schneller Wisch -, laese `beiEnde` noch `null` und die Geste waere
|
||||
verloren. Am 04.09.2026 gemessen: ein voller Wisch tat gar nichts. */
|
||||
const [zug, setzeZugZustand] = useState<number | null>(null)
|
||||
const zugRef = useRef<number | null>(null)
|
||||
const setzeZug = (wert: number | null) => {
|
||||
zugRef.current = wert
|
||||
setzeZugZustand(wert)
|
||||
}
|
||||
const start = useRef<{ x: number; y: number } | null>(null)
|
||||
const entschieden = useRef(false)
|
||||
const rahmen = useRef<HTMLDivElement | null>(null)
|
||||
|
||||
const versatz = zug ?? (offen ? -WISCH_BREITE : 0)
|
||||
const vollAb = () => {
|
||||
const breite = rahmen.current?.offsetWidth ?? 0
|
||||
return -Math.min(WISCH_VOLL_MAX, Math.max(WISCH_VOLL_MIN, breite * WISCH_VOLL_ANTEIL))
|
||||
}
|
||||
const scharf = versatz <= vollAb()
|
||||
|
||||
const beiStart = (e: ReactTouchEvent<HTMLDivElement>) => {
|
||||
if (e.touches.length !== 1) return
|
||||
@@ -389,13 +421,24 @@ export function WischLoeschen({
|
||||
entschieden.current = true
|
||||
}
|
||||
const basis = offen ? -WISCH_BREITE : 0
|
||||
setzeZug(Math.max(-WISCH_BREITE, Math.min(0, basis + dx)))
|
||||
// Nicht mehr bei WISCH_BREITE abschneiden: ohne den weiteren Weg gibt
|
||||
// es keinen vollen Wisch und keine Rueckmeldung, dass er loescht.
|
||||
setzeZug(Math.min(0, basis + dx))
|
||||
}
|
||||
|
||||
const beiEnde = () => {
|
||||
if (zug !== null) setzeOffen(zug < -WISCH_BREITE / 2)
|
||||
const versatzJetzt = zugRef.current
|
||||
start.current = null
|
||||
setzeZug(null)
|
||||
if (versatzJetzt === null) return
|
||||
if (versatzJetzt <= vollAb()) {
|
||||
// Ganz durchgezogen: jetzt handeln. `beiLoeschen` ist der Handler des
|
||||
// Aufrufers und fragt dort nach, wo die Daten nicht wiederkommen.
|
||||
setzeOffen(false)
|
||||
beiLoeschen()
|
||||
return
|
||||
}
|
||||
setzeOffen(versatzJetzt < -WISCH_BREITE / 2)
|
||||
}
|
||||
|
||||
// Sichtbar erst, wenn wirklich gewischt wird. Ohne das liegt der rote
|
||||
@@ -406,10 +449,15 @@ export function WischLoeschen({
|
||||
const zeigt = offen || zug !== null
|
||||
|
||||
return (
|
||||
<div className={"dm-wisch" + (zeigt ? " dm-wisch--zeigt" : "")}>
|
||||
<div className={"dm-wisch" + (zeigt ? " dm-wisch--zeigt" : "")} ref={rahmen}>
|
||||
<button
|
||||
type="button"
|
||||
className="dm-wisch__loeschen"
|
||||
// Die Flaeche waechst mit dem Zug - je weiter gezogen, desto mehr
|
||||
// Rot. Dieselbe Lesart wie auf iOS: Farbe heisst entschieden.
|
||||
style={{ width: Math.max(WISCH_BREITE, -versatz) }}
|
||||
className={
|
||||
"dm-wisch__loeschen" + (scharf ? " dm-wisch__loeschen--scharf" : "")
|
||||
}
|
||||
tabIndex={offen ? 0 : -1}
|
||||
aria-hidden={!offen}
|
||||
onClick={() => {
|
||||
@@ -452,3 +500,40 @@ export function gruppenErgebnis(punkte: readonly SicherheitsPunkt[]): boolean |
|
||||
if (punkte.length === 0 || punkte.some((p) => p.ok === null)) return null
|
||||
return punkte.every((p) => p.ok === true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Markenlogo für die SmartDeal-Anzeige.
|
||||
*
|
||||
* RENDERT NICHTS, WENN ES KEINE ADRESSE GIBT. Ohne gespeicherten Zugang gibt
|
||||
* `markenlogoUrl()` `undefined` zurück; React lässt `src` dann weg — und ein
|
||||
* `<img>` ohne `src` zeigt das **kaputte Bildsymbol**. Der `onError`-Rettungs-
|
||||
* anker greift dabei nicht: ohne Adresse findet gar kein Ladeversuch statt,
|
||||
* also gibt es auch kein Fehlerereignis. Am 04.09.2026 auf der Musterseite
|
||||
* gesehen, samt Alternativtext „Shell" neben dem kaputten Symbol.
|
||||
*
|
||||
* `onError` bleibt trotzdem stehen — für den anderen Fall: die Adresse ist da,
|
||||
* die Datei fehlt auf dem Server.
|
||||
*/
|
||||
export function Markenlogo({
|
||||
alt = "",
|
||||
className,
|
||||
style,
|
||||
}: {
|
||||
alt?: string
|
||||
className?: string
|
||||
style?: CSSProperties
|
||||
}) {
|
||||
const url = markenlogoUrl()
|
||||
if (!url) return null
|
||||
return (
|
||||
<img
|
||||
src={url}
|
||||
alt={alt}
|
||||
{...(className ? { className } : {})}
|
||||
{...(style ? { style } : {})}
|
||||
onError={(e) => {
|
||||
e.currentTarget.style.display = "none"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -167,7 +167,21 @@ export function bildUrl(dateiname: string | null | undefined): string | undefine
|
||||
if (!dateiname) return undefined
|
||||
const basis = zugangLesenSynchron()?.basisUrl
|
||||
if (!basis) return undefined
|
||||
return `${basis}/local/bilder/${dateiname}?v=${bildStand(dateiname)}`
|
||||
const stand = bildStand(dateiname)
|
||||
/* Stand 0 heisst: dieses Foto gibt es nicht (siehe bildStand). Dann gar
|
||||
keine Adresse - der Platzhalter steht ohnehin bereit.
|
||||
|
||||
Bis zum 04.09.2026 wurde die Adresse trotzdem gebaut, und der Browser
|
||||
holte bei JEDEM Rendern eine 404. Live gemessen: fuenf Anfragen fuer
|
||||
front-schraeg.webp in einem einzigen Ladevorgang, dazu heckansicht,
|
||||
cockpit, sitze und seitenansicht-winter. Die App weiss laengst, dass
|
||||
es sie nicht gibt; sie muss nicht danach fragen.
|
||||
|
||||
Nur wenn das Backend gar keine Staende mitliefert (bildStaende null,
|
||||
Rueckfall auf bildVersion), wird weiter probiert - dort ist 0 kein
|
||||
moeglicher Wert und die Abwesenheit unbekannt. */
|
||||
if (bildStaende && stand === 0) return undefined
|
||||
return `${basis}/local/bilder/${dateiname}?v=${stand}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,6 +18,11 @@ body {
|
||||
}
|
||||
|
||||
body {
|
||||
/* Die Schrift steht sonst nur auf .ads-root. Alles ausserhalb - und sei
|
||||
es nur eine Fehlermeldung des Browsers - fiel damit auf die
|
||||
Vorgabeschrift zurueck, am 04.09.2026 als Times New Roman gemessen.
|
||||
Sichtbar war heute nichts; die Flanke bleibt trotzdem offen. */
|
||||
font-family: var(--font-stack, Helvetica, Arial, sans-serif);
|
||||
background: var(--canvas);
|
||||
}
|
||||
|
||||
|
||||
@@ -350,3 +350,31 @@
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Apple HIG: 44x44 pt als kleinstes Tippziel. Der Pfeil SIEHT weiterhin
|
||||
34x34 aus - die Trefferflaeche liegt unsichtbar darum.
|
||||
|
||||
Warum ein Overlay statt groesserer Masse: die Kopfzeile ist ueber viele
|
||||
Sitzungen am gerenderten Bild abgestimmt worden, und der Titel steht nur
|
||||
deshalb wirklich mittig, weil links und rechts genau 34px liegen (siehe
|
||||
.dm-kopf__aktion daneben). Groessere Knoepfe wuerden das verschieben.
|
||||
|
||||
Am 04.09.2026 gemessen: 34x34 ohne jede Vergroesserung - beim
|
||||
meistbenutzten Bedienelement der App. Wortgleich mit .ads-icon-button im
|
||||
Design-System und #back im Panel. */
|
||||
.dm-kopf__zurueck,
|
||||
.dm-kopf__aktion {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dm-kopf__zurueck::after,
|
||||
.dm-kopf__aktion::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: var(--r-pill);
|
||||
}
|
||||
|
||||
@@ -158,7 +158,11 @@ input[type="date"].dm-eingabe {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 32px;
|
||||
/* Seitlich 20 statt 32: auf 375 px blieben fuer die Beschriftung nur
|
||||
87 px, und "Server-Adresse" brach dadurch am Bindestrich um -
|
||||
ausgerechnet auf dem allerersten Bildschirm (gemessen 04.09.2026).
|
||||
Oben und unten bleibt es bei 32. */
|
||||
padding: 32px 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@@ -397,7 +401,7 @@ input[type="date"].dm-eingabe {
|
||||
visibility: hidden;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: var(--bad);
|
||||
background: var(--bad-flaeche, var(--bad));
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
font-size: 15px;
|
||||
@@ -408,6 +412,13 @@ input[type="date"].dm-eingabe {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
/* Voll durchgewischt: die Flaeche wird kraeftiger, damit vor dem Loslassen
|
||||
ablesbar ist, dass jetzt geloescht wird und nicht nur aufgeklappt.
|
||||
Wortgleich mit .ads-swipe-delete--armed im Design-System. */
|
||||
.dm-wisch__loeschen--scharf {
|
||||
filter: brightness(1.12);
|
||||
}
|
||||
|
||||
.dm-wisch__inhalt {
|
||||
position: relative;
|
||||
/* Deckend, NICHT --tile: die Zeile liegt auf einer Kachel, und die ist
|
||||
@@ -2176,3 +2187,58 @@ button.dm-listenzeile {
|
||||
.dm-debugknopf:active {
|
||||
color: var(--fg2);
|
||||
}
|
||||
|
||||
/* --- Apple HIG: 44x44 pt als kleinstes Tippziel -------------------------
|
||||
Am 04.09.2026 gemessen und hier nachgezogen. Zwei Wege, je nach Element:
|
||||
|
||||
(a) Unsichtbares Overlay, wo die sichtbare Groesse Teil des Bildes ist -
|
||||
der Griff des Standort-Blatts (36x4) und die Zahnrad-Knoepfe.
|
||||
(b) Echte Mindesthoehe, wo das Element ohnehin eine Flaeche ist und 44px
|
||||
nicht stoeren - Eingabefelder, Auswahlfelder, Pillen, Segmente.
|
||||
|
||||
(b) veraendert das Bild sichtbar: Formulare werden etwas hoeher. Das ist
|
||||
Absicht - 36px hohe Eingabefelder sind auf dem Telefon zu knapp. */
|
||||
|
||||
.dm-standortmenu__griff {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dm-standortmenu__griff::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 88px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.dm-standort-pille {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.dm-montiert {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.ads-feld input,
|
||||
.ads-feld select,
|
||||
input.dm-eingabe,
|
||||
select.dm-auswahl {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
/* Das Druckfeld ist schmal (58px), aber die Hoehe gilt auch dort. */
|
||||
.ads-feld input.druckfeld {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.ads-seg button {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
/* Der Schieberegler traegt seinen Daumen selbst; die Bahn ist duenn, das
|
||||
Element darum darf trotzdem 44px hoch treffen. */
|
||||
input[type="range"] {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user