Ziehen zum Aktualisieren, Nachtmodus-Zeilen, Parkplatznamen (2026.8.30.10)
Ziehen zum Aktualisieren funktionierte auf keinem Geraet, in keiner der beiden Anwendungen - und der Grund entwertet zugleich, wie es bisher geprueft wurde. Beide trieben die Geste ueber pointermove und riefen dort preventDefault(). Das verhindert laut Spezifikation kein Scrollen; darueber entscheidet allein touch-action. Auf echtem Touch uebernimmt der Browser die senkrechte Geste, schickt pointercancel und stellt pointermove ein - der Zug wird zurueckgesetzt, lange bevor er die Schwelle erreicht. Eine Maus kennt diese Uebernahme nicht, synthetische PointerEvents ebenso wenig: genau deshalb lief die Geste 2026-08-17 im Test sauber durch und am Geraet nie. Jetzt laeuft sie auf Touch-Ereignissen, touchmove zwingend mit passive:false (sonst ist preventDefault() wirkungslos); der Zeigerweg bleibt fuer Maus und Stift und ignoriert pointerType "touch". Mit echten TouchEvents geprueft, inklusive defaultPrevented - dem einzigen Signal, das belegt, dass das native Ueberscrollen wirklich unterbunden ist. Der zuletzt nicht nachstellbare Befund war nachtmodus-spezifisch: --tile ist dort ein durchscheinender Schleier, und die Wischzeilen malten ihn ein zweites Mal auf die Kachel, auf der sie ohnehin liegen. Am Tag sind beide deckend weiss, deshalb war nichts zu sehen. Das Panel nutzt an dieser Stelle --tile-deckend; dieses Token fehlte im Design-System und ist jetzt da. Ausserdem: steht das Auto auf einem benannten Platz, zeigen beide dessen Namen statt der naechsten Hausnummer - Nominatim liefert ihn mit, die Pruefung auf die Art der Flaeche verhindert, dass irgendein benanntes Gebaeude die Anschrift verdraengt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# AGENTS.md — Project state, review findings, open items, and working rules
|
||||
|
||||
**Last updated: 2026-08-30** (owner findings batch - vehicle pin, tile chevrons, select arrows,
|
||||
SmartDeal dialog, address cache and pull-to-refresh; manifest `2026.8.30.9`, see section AO.
|
||||
SmartDeal dialog, address cache and pull-to-refresh; manifest `2026.8.30.10`, see section AO.
|
||||
Before that: design-audit follow-through - the five priority items applied to
|
||||
both codebases, plus the location preview's wrong pin; manifest `2026.8.30.8`, see section AN.
|
||||
Before that: third parity round — the panel had never actually rendered Audi
|
||||
@@ -5879,6 +5879,57 @@ vs 1.1 right, measured via `getBBox`) and is now nudged onto centre and scaled t
|
||||
outweighing its outline neighbours; Teilen uses the real `share-s` from the CI set; and the sheet's
|
||||
close button only exists while the sheet is raised - half-covered it looked operable and did nothing.
|
||||
|
||||
**The "could not be reproduced" finding was a night-mode-only defect (2026.8.30.10).** The owner
|
||||
added the missing detail: night mode. In night mode `--tile` is deliberately translucent - a white
|
||||
veil over the dark page - and `.ads-swipe-content` painted the same token a second time on top of
|
||||
the tile it already sits on. Two veils add up, so every row in Fahrten and Tanken was visibly lighter
|
||||
than the tile around it, while the year and month headers (not swipe rows) stayed correct. In day
|
||||
mode both values are opaque white, which is why painting white on white showed nothing and the first
|
||||
pass found the two sides identical. The panel does not have this: its iOS overlay sets
|
||||
`.swipe-wrap .swipe-content{background:var(--tile-deckend)}`, an opaque token that `design-system`
|
||||
never had. Added `--tile-deckend` (#171B21 / #FFFFFF, the panel's own values) and switched
|
||||
`SwipeRow.css` to it. **Rule worth keeping: any surface painted on top of another surface needs the
|
||||
opaque token, never `--tile`** - the bug is invisible in day mode by construction.
|
||||
|
||||
**Pull-to-refresh never worked on a real device, in either app - and the reason invalidates how it
|
||||
was verified before.** Both implementations drove the gesture from `pointermove` and called
|
||||
`preventDefault()` there. Per the Pointer Events spec that does **not** prevent scrolling: scrolling
|
||||
is decided by `touch-action` alone. On a real touch screen the browser claims the vertical gesture,
|
||||
fires `pointercancel` and stops sending `pointermove`; the handler's own cancel path then resets the
|
||||
drag long before the 56px threshold. A mouse never triggers that takeover, and neither do synthetic
|
||||
`PointerEvent`s - which is precisely why the 2026-08-17 session drove the whole gesture end to end
|
||||
with dispatched pointer events, saw it work, and concluded the JS was fine. It was fine; it was
|
||||
attached to the wrong event family. The `overscroll-behavior-y: contain` added then addresses scroll
|
||||
*chaining* to the host page, a different problem, and was left in place.
|
||||
|
||||
Both now run the gesture on touch events - `touchstart` passive, `touchmove` with
|
||||
**`{ passive: false }`** (without that flag `preventDefault()` is ignored and the browser scrolls
|
||||
anyway), `touchend`/`touchcancel` - while the pointer path stays for mouse and stylus and now
|
||||
ignores `pointerType === "touch"` so the two cannot fight over the same gesture. Panel: `ptrStarten()`
|
||||
/ `ptrBewegen()` / `ptrBeenden()` extracted so both paths share one implementation. App: a manual
|
||||
listener in a `useEffect`, because React registers `touchmove` passively and `preventDefault()` in an
|
||||
`onTouchMove` prop would be silently ignored. Verified in both with genuine `TouchEvent`s - which the
|
||||
old code could not have satisfied: threshold reached, label switching to "Loslassen zum
|
||||
Aktualisieren", the refresh running, and `defaultPrevented === true` on the move, the one signal that
|
||||
proves the native overscroll is actually suppressed.
|
||||
|
||||
One trap avoided while doing it: the app's early `return` for the wide layout sat in front of the new
|
||||
`useEffect`, which would have made the hook run on some renders and not others. Moved behind it.
|
||||
|
||||
**Named places instead of a street address.** On the owner's question - a car park says more than the
|
||||
nearest house number. Nominatim already returns `name` plus the type of the matched feature; both
|
||||
apps now prefer that name when the type is one of parking / parking_space / parking_entrance /
|
||||
bicycle_parking / motorcycle_parking / rest_area / services / fuel / charging_station, with the town
|
||||
still appended. The type check is what keeps any incidentally named building from displacing the
|
||||
address. And to answer the other half plainly: when nothing resolves at all, both show the
|
||||
coordinates - that was already the designed fallback (`adresse ?? koordinaten`), and it is also what
|
||||
a throttled lookup produces, which is exactly why the two cases were indistinguishable before the
|
||||
cache landed.
|
||||
|
||||
**Header padding on the app was cut to the safe area on the owner's instruction** - see the note at
|
||||
the end of section AN; the panel's 56px sit below Home Assistant's own header, the native shell has
|
||||
nothing above them but the status bar.
|
||||
|
||||
**One finding could not be reproduced:** "Fahrten/Tankfüllung: Auflistung/grauer Hintergrund ist
|
||||
falsch". Panel and app are identical there - same backgrounds (transparent rows on a white tile),
|
||||
same paddings, same indents, same text. Reported back rather than guessed at.
|
||||
|
||||
+65
-18
@@ -53,12 +53,8 @@ function useZiehenAktualisieren(
|
||||
const [laedt, setzeLaedt] = useState(false)
|
||||
const start = useRef<number | null>(null)
|
||||
|
||||
if (!beiAktualisieren) {
|
||||
return { zug: 0, laedt: false, handler: {} as Record<string, never> }
|
||||
}
|
||||
|
||||
const beenden = () => {
|
||||
if (start.current === null) return
|
||||
if (start.current === null || !beiAktualisieren) return
|
||||
start.current = null
|
||||
if (zug >= PTR_SCHWELLE) {
|
||||
setzeLaedt(true)
|
||||
@@ -71,26 +67,77 @@ function useZiehenAktualisieren(
|
||||
}
|
||||
}
|
||||
|
||||
const starten = (y: number) => {
|
||||
if (laedt) return
|
||||
if ((inhalt.current?.scrollTop ?? 0) > 0) return
|
||||
start.current = y
|
||||
setzeZug(0)
|
||||
}
|
||||
/* Gibt true zurück, wenn die Geste uns gehört — der Aufrufer muss dann
|
||||
preventDefault() rufen. */
|
||||
const bewegen = (y: number): boolean => {
|
||||
if (start.current === null) return false
|
||||
const dy = y - start.current
|
||||
if (dy > 0 && (inhalt.current?.scrollTop ?? 0) <= 0) {
|
||||
// Widerstand wie bei iOS: halbe Strecke, gedeckelt bei 90px.
|
||||
setzeZug(Math.min(90, dy * 0.5))
|
||||
return true
|
||||
}
|
||||
start.current = null
|
||||
setzeZug(0)
|
||||
return false
|
||||
}
|
||||
|
||||
/* Auf dem Finger läuft die Geste über Touch-Ereignisse, nicht über Pointer.
|
||||
Grund: preventDefault() auf einem Pointer-Ereignis verhindert laut
|
||||
Spezifikation kein Scrollen — darüber entscheidet allein touch-action. Auf
|
||||
einem echten Gerät übernimmt der Browser die senkrechte Geste deshalb
|
||||
sofort, schickt pointercancel und stellt pointermove ein; der Zug wurde
|
||||
zurückgesetzt, bevor er die Schwelle erreichte. Am Rechner fällt das nie
|
||||
auf, eine Maus kennt keine Scroll-Übernahme. Der Listener muss von Hand
|
||||
und mit passive:false hängen — React registriert touchmove passiv, dort
|
||||
wäre preventDefault() wirkungslos. */
|
||||
useEffect(() => {
|
||||
const el = inhalt.current
|
||||
// Der Aussteiger sitzt IM Effekt, nicht davor: ein vorgezogenes return
|
||||
// wuerde diesen Hook je nach Bildschirmbreite mal aufrufen und mal nicht
|
||||
// und damit die Hook-Reihenfolge brechen.
|
||||
if (!el || !beiAktualisieren) return undefined
|
||||
const beiStart = (e: TouchEvent) => {
|
||||
if (e.touches.length === 1) starten(e.touches[0]!.clientY)
|
||||
}
|
||||
const beiBewegung = (e: TouchEvent) => {
|
||||
if (e.touches.length !== 1) return
|
||||
if (bewegen(e.touches[0]!.clientY)) e.preventDefault()
|
||||
}
|
||||
el.addEventListener("touchstart", beiStart, { passive: true })
|
||||
el.addEventListener("touchmove", beiBewegung, { passive: false })
|
||||
el.addEventListener("touchend", beenden)
|
||||
el.addEventListener("touchcancel", beenden)
|
||||
return () => {
|
||||
el.removeEventListener("touchstart", beiStart)
|
||||
el.removeEventListener("touchmove", beiBewegung)
|
||||
el.removeEventListener("touchend", beenden)
|
||||
el.removeEventListener("touchcancel", beenden)
|
||||
}
|
||||
})
|
||||
|
||||
if (!beiAktualisieren) {
|
||||
return { zug: 0, laedt: false, handler: {} as Record<string, never> }
|
||||
}
|
||||
|
||||
return {
|
||||
zug,
|
||||
laedt,
|
||||
handler: {
|
||||
// Nur Maus/Stift — der Finger läuft über die Touch-Ereignisse oben.
|
||||
onPointerDown: (e: ReactPointerEvent) => {
|
||||
if (!e.isPrimary || laedt) return
|
||||
if ((inhalt.current?.scrollTop ?? 0) > 0) return
|
||||
start.current = e.clientY
|
||||
setzeZug(0)
|
||||
if (!e.isPrimary || e.pointerType === "touch") return
|
||||
starten(e.clientY)
|
||||
},
|
||||
onPointerMove: (e: ReactPointerEvent) => {
|
||||
if (start.current === null || !e.isPrimary) return
|
||||
const dy = e.clientY - start.current
|
||||
if (dy > 0 && (inhalt.current?.scrollTop ?? 0) <= 0) {
|
||||
// Widerstand wie bei iOS: halbe Strecke, gedeckelt bei 90px.
|
||||
setzeZug(Math.min(90, dy * 0.5))
|
||||
} else {
|
||||
start.current = null
|
||||
setzeZug(0)
|
||||
}
|
||||
if (!e.isPrimary || e.pointerType === "touch") return
|
||||
bewegen(e.clientY)
|
||||
},
|
||||
onPointerUp: beenden,
|
||||
onPointerCancel: beenden,
|
||||
|
||||
@@ -23,6 +23,20 @@ export interface Koordinate {
|
||||
|
||||
const CACHE_SCHLUESSEL = "dm360.geocache"
|
||||
|
||||
/* Arten, bei denen der Eigenname aussagekräftiger ist als die Anschrift
|
||||
(Feld type der Nominatim-Antwort). */
|
||||
const PLATZ_ARTEN = new Set([
|
||||
"parking",
|
||||
"parking_space",
|
||||
"parking_entrance",
|
||||
"bicycle_parking",
|
||||
"motorcycle_parking",
|
||||
"rest_area",
|
||||
"services",
|
||||
"fuel",
|
||||
"charging_station",
|
||||
])
|
||||
|
||||
function cacheLesen(): Record<string, Koordinate | string | null> {
|
||||
try {
|
||||
const roh = localStorage.getItem(CACHE_SCHLUESSEL)
|
||||
@@ -75,13 +89,25 @@ export async function koordinatenAufloesen(lat: number, lon: number): Promise<st
|
||||
{ headers: { "Accept-Language": "de" } },
|
||||
)
|
||||
const daten = (await antwort.json()) as {
|
||||
name?: string
|
||||
type?: string
|
||||
address?: Record<string, string>
|
||||
display_name?: string
|
||||
}
|
||||
const a = daten.address ?? {}
|
||||
const strasse = [a["road"], a["house_number"]].filter(Boolean).join(" ")
|
||||
const ort = [a["postcode"], a["city"] ?? a["town"] ?? a["village"]].filter(Boolean).join(" ")
|
||||
const adresse = [strasse, ort].filter(Boolean).join(", ") || daten.display_name || null
|
||||
/* Steht das Auto auf einem benannten Platz — Parkplatz, Parkhaus,
|
||||
Rastplatz, Ladesäule —, sagt dessen Name mehr als die nächstgelegene
|
||||
Hausnummer: "Parkhaus Westentor" statt "Rathausstraße 4". Nominatim
|
||||
liefert ihn im Feld name, zusammen mit der Art der getroffenen Fläche;
|
||||
ohne die Prüfung auf diese Art würde auch jedes beliebige Gebäude, das
|
||||
zufällig einen Namen trägt, die Anschrift verdrängen. Der Ort bleibt
|
||||
angehängt, damit erkennbar ist, wo der Platz liegt. */
|
||||
const platz = PLATZ_ARTEN.has(daten.type ?? "") ? (daten.name ?? "").trim() : ""
|
||||
const adresse = platz
|
||||
? [platz, ort].filter(Boolean).join(", ")
|
||||
: [strasse, ort].filter(Boolean).join(", ") || daten.display_name || null
|
||||
// Nur echte Treffer merken: eine gedrosselte Antwort ist kein "hier gibt
|
||||
// es keine Adresse", die soll beim nächsten Mal erneut versucht werden.
|
||||
if (adresse) adresseMerken(schluessel, adresse)
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":"2026.8.30.9","sha256":"63b67b2116bdc2970ebcb9c02dc8c2d1ec1f0156fd70c59f88db101307054474","bytes":256695,"gebaut":"2026-08-30T21:09:06Z"}
|
||||
{"version":"2026.8.30.10","sha256":"0a9cdbbccf4e91b07152699e0331bf75ff62908832880e6c2408981b76347710","bytes":257008,"gebaut":"2026-08-30T21:20:20Z"}
|
||||
Binary file not shown.
@@ -786,6 +786,10 @@ function standortErfassenFallsNoetig() {
|
||||
dokumentierte Leaflet-über-CDN-Vorgehen (siehe AGENTS.md). Cache-Schlüssel
|
||||
auf 3 Nachkommastellen (~110 m) gerundet, damit ein erneutes Öffnen an
|
||||
derselben Stelle keinen zweiten Abruf auslöst. */
|
||||
/* Arten, bei denen der Eigenname aussagekraeftiger ist als die Anschrift. */
|
||||
const PLATZ_ARTEN = new Set(["parking", "parking_space", "parking_entrance",
|
||||
"bicycle_parking", "motorcycle_parking", "rest_area", "services", "fuel",
|
||||
"charging_station"]);
|
||||
async function standortAdresseAufloesen(lat, lon) {
|
||||
const key = lat.toFixed(3) + "," + lon.toFixed(3);
|
||||
if (STANDORT_ADRESSE_KEY === key) return STANDORT_ADRESSE;
|
||||
@@ -808,7 +812,16 @@ async function standortAdresseAufloesen(lat, lon) {
|
||||
const daten = await res.json();
|
||||
const a = daten.address || {};
|
||||
const strasse = [a.road, a.house_number].filter(Boolean).join(" ");
|
||||
STANDORT_ADRESSE = [strasse, [a.postcode, a.city || a.town || a.village].filter(Boolean).join(" ")].filter(Boolean).join(", ") || daten.display_name || "";
|
||||
const ort = [a.postcode, a.city || a.town || a.village].filter(Boolean).join(" ");
|
||||
/* Steht das Auto auf einem benannten Platz - Parkplatz, Parkhaus,
|
||||
Rastplatz -, sagt dessen Name mehr als die naechstgelegene Hausnummer.
|
||||
Nominatim liefert ihn im Feld name; die Pruefung auf die Art der
|
||||
Flaeche verhindert, dass jedes zufaellig benannte Gebaeude die
|
||||
Anschrift verdraengt. */
|
||||
const platz = PLATZ_ARTEN.has(daten.type) ? (daten.name || "").trim() : "";
|
||||
STANDORT_ADRESSE = platz
|
||||
? [platz, ort].filter(Boolean).join(", ")
|
||||
: [strasse, ort].filter(Boolean).join(", ") || daten.display_name || "";
|
||||
} catch (e) {
|
||||
// Den Schluessel bewusst NICHT setzen: sonst gilt ein einzelner
|
||||
// fehlgeschlagener Abruf (Rate-Limit, kurzer Netzausfall) fuer diese Zelle
|
||||
@@ -4803,32 +4816,16 @@ function ereignisseVerdrahten() {
|
||||
ROOT.addEventListener("pointerdown", (e) => {
|
||||
// Ziehen zum Aktualisieren (nur Übersicht, nur wenn ganz oben gescrollt) -
|
||||
// unabhängig von der Wischgeste unten, da hier keine .swipe-wrap nötig ist.
|
||||
if (e.isPrimary && route.name === "home" && !ptrLaedt) {
|
||||
const ansicht = $("#view");
|
||||
if (ansicht && ansicht.scrollTop <= 0) { ptrY0 = e.clientY; ptrDrag = 0; }
|
||||
}
|
||||
// Nur Maus/Stift. Der Finger laeuft ueber die Touch-Ereignisse weiter
|
||||
// unten, weil nur dort preventDefault() das native Ueberscrollen wirklich
|
||||
// verhindert - siehe ptrBewegen().
|
||||
if (e.isPrimary && e.pointerType !== "touch") ptrStarten(e.clientY);
|
||||
const wrap = e.target.closest(".swipe-wrap");
|
||||
if (!wrap || !e.isPrimary) return;
|
||||
swipeAktivWrap = wrap; swipeStartX = e.clientX; swipeStartY = e.clientY; swipeGeste = null; swipeDX = 0;
|
||||
});
|
||||
ROOT.addEventListener("pointermove", (e) => {
|
||||
if (ptrY0 !== null && e.isPrimary) {
|
||||
const ansicht = $("#view"), dy = e.clientY - ptrY0;
|
||||
if (dy > 0 && ansicht && ansicht.scrollTop <= 0) {
|
||||
e.preventDefault();
|
||||
ptrDrag = Math.min(90, dy * 0.5); // Widerstand, wie bei iOS
|
||||
const ptr = $("#ptr");
|
||||
if (ptr) {
|
||||
ptr.style.transition = "none";
|
||||
ptr.style.height = ptrDrag + "px";
|
||||
ptr.querySelector("span").textContent = ptrDrag >= 56 ? "Loslassen zum Aktualisieren" : "Ziehen zum Aktualisieren";
|
||||
}
|
||||
} else {
|
||||
ptrY0 = null; ptrDrag = 0;
|
||||
const ptr = $("#ptr");
|
||||
if (ptr) { ptr.style.transition = ""; ptr.style.height = "0"; }
|
||||
}
|
||||
}
|
||||
if (e.pointerType !== "touch") ptrBewegen(e.clientY);
|
||||
if (!swipeAktivWrap || !e.isPrimary) return;
|
||||
const dx = e.clientX - swipeStartX, dy = e.clientY - swipeStartY;
|
||||
if (swipeGeste === null) {
|
||||
@@ -4863,6 +4860,39 @@ function ereignisseVerdrahten() {
|
||||
// auf den gerade erst aufgedeckten Löschen-Knopf) fälschlich verschluckt.
|
||||
if (swipeWarGezogen) setTimeout(() => { swipeWarGezogen = false; }, 0);
|
||||
}
|
||||
function ptrStarten(y) {
|
||||
if (route.name !== "home" || ptrLaedt) return;
|
||||
const ansicht = $("#view");
|
||||
if (ansicht && ansicht.scrollTop <= 0) { ptrY0 = y; ptrDrag = 0; }
|
||||
}
|
||||
/* Gibt true zurueck, wenn die Geste uns gehoert - der Aufrufer muss dann
|
||||
preventDefault() rufen. Warum das ueber Touch-Ereignisse laufen muss:
|
||||
preventDefault() auf einem Pointer-Ereignis verhindert laut Spezifikation
|
||||
KEIN Scrollen; darueber entscheidet allein touch-action. Auf einem echten
|
||||
Touch-Geraet uebernimmt der Browser die senkrechte Geste deshalb sofort,
|
||||
schickt pointercancel und stellt pointermove ein - die Geste wurde
|
||||
zurueckgesetzt, bevor sie die 56px erreichen konnte. Am Rechner faellt das
|
||||
nie auf, weil eine Maus keine Scroll-Uebernahme kennt; auch synthetische
|
||||
PointerEvents durchlaufen sie nicht. Auf touchmove dagegen wirkt
|
||||
preventDefault() und haelt das native Ueberscrollen an. */
|
||||
function ptrBewegen(y) {
|
||||
if (ptrY0 === null) return false;
|
||||
const ansicht = $("#view"), dy = y - ptrY0;
|
||||
if (dy > 0 && ansicht && ansicht.scrollTop <= 0) {
|
||||
ptrDrag = Math.min(90, dy * 0.5); // Widerstand, wie bei iOS
|
||||
const ptr = $("#ptr");
|
||||
if (ptr) {
|
||||
ptr.style.transition = "none";
|
||||
ptr.style.height = ptrDrag + "px";
|
||||
ptr.querySelector("span").textContent = ptrDrag >= 56 ? "Loslassen zum Aktualisieren" : "Ziehen zum Aktualisieren";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
ptrY0 = null; ptrDrag = 0;
|
||||
const ptr = $("#ptr");
|
||||
if (ptr) { ptr.style.transition = ""; ptr.style.height = "0"; }
|
||||
return false;
|
||||
}
|
||||
function ptrBeenden() {
|
||||
if (ptrY0 === null) return;
|
||||
ptrY0 = null;
|
||||
@@ -4886,6 +4916,19 @@ function ereignisseVerdrahten() {
|
||||
ROOT.addEventListener("pointerup", (e) => { swipeBeenden(); ptrBeenden(); });
|
||||
ROOT.addEventListener("pointercancel", (e) => { swipeBeenden(); ptrBeenden(); });
|
||||
|
||||
// Ziehen zum Aktualisieren auf dem Finger: eigener Weg ueber Touch-Ereignisse
|
||||
// (Begruendung in ptrBewegen()). passive:false ist zwingend - ohne das ist
|
||||
// preventDefault() auf touchmove wirkungslos und der Browser scrollt doch.
|
||||
ROOT.addEventListener("touchstart", (e) => {
|
||||
if (e.touches.length === 1) ptrStarten(e.touches[0].clientY);
|
||||
}, { passive: true });
|
||||
ROOT.addEventListener("touchmove", (e) => {
|
||||
if (e.touches.length !== 1) return;
|
||||
if (ptrBewegen(e.touches[0].clientY)) e.preventDefault();
|
||||
}, { passive: false });
|
||||
ROOT.addEventListener("touchend", () => ptrBeenden());
|
||||
ROOT.addEventListener("touchcancel", () => ptrBeenden());
|
||||
|
||||
// Batteriespannungs-Verlauf: Pinch-Zoom (2 Finger) / Ziehen zum Verschieben
|
||||
// (1 Finger) / Mausrad-Zoom, per pointerId in bvZeiger nachverfolgt statt
|
||||
// über die swipe*-Variablen oben, die für horizontales Wischen in Listen
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"domain": "audi_dashboard",
|
||||
"name": "Audi Dashboard",
|
||||
"version": "2026.8.30.9",
|
||||
"version": "2026.8.30.10",
|
||||
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
|
||||
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
|
||||
"codeowners": ["@paul"],
|
||||
|
||||
@@ -37,7 +37,11 @@
|
||||
|
||||
.ads-swipe-content {
|
||||
position: relative;
|
||||
background: var(--tile);
|
||||
/* Deckend, nicht --tile: die Zeile liegt auf einer Kachel, die im
|
||||
Nachtmodus selbst nur ein durchscheinender Schleier ist - zweimal
|
||||
gemalt wird der Streifen sichtbar heller als die Kachel um ihn herum
|
||||
(.swipe-wrap .swipe-content in audi-dashboard-ios.css). */
|
||||
background: var(--tile-deckend);
|
||||
touch-action: pan-y;
|
||||
transition: transform .18s;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,13 @@
|
||||
/* Graue Füllfläche für Bedienelemente (Seg-Spur, Eingabe-/Auswahl-Boxen) -
|
||||
live am Panel gemessen (--ios-fill, audi-dashboard-ios.css). */
|
||||
--ios-fill: rgba(120, 120, 128, .24);
|
||||
/* Deckende Kachelflaeche. Im Nachtmodus ist --tile bewusst durchscheinend
|
||||
(weisser Schleier ueber der dunklen Seite) - wird sie ein zweites Mal
|
||||
auf eine Kachel gemalt, addieren sich die Schleier und der Bereich wird
|
||||
sichtbar heller. Genau das passierte den Wischzeilen in Fahrten und
|
||||
Tanken. Ueberall dort, wo eine Flaeche auf einer anderen liegt, gehoert
|
||||
dieser deckende Wert hin (--tile-deckend im Panel). */
|
||||
--tile-deckend: #171B21;
|
||||
|
||||
--font-stack: Helvetica, Arial, sans-serif;
|
||||
}
|
||||
@@ -69,6 +76,7 @@
|
||||
--bad: #FF3B30;
|
||||
--shade: rgba(0, 0, 0, .04);
|
||||
--ios-fill: rgba(120, 120, 128, .16);
|
||||
--tile-deckend: #FFFFFF;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user