PWA: vis dyplenket tilfluktsrom nederst i lista med badge (hybrid)
Speiler Android-fiksen i 1fb9f14/337e9b9. Når en dyplenke eller markørtap velger et tilfluktsrom utenfor de N nærmeste, ble det tidligere unshift-et inn på indeks 0 og framstod som nærmest — uten forklaring. Nå: - app.ts: push i stedet for unshift på begge steder, valgt indeks = siste; ny modultilstand outsideNearestRomnr og renderShelterList()-helper - shelter-list.ts: updateList() tar outsideNearestRomnr, tegner badge på den raden, prefikser badge-teksten i aria-label (samme rekkefølge som contentDescription i ShelterListAdapter.kt) og scrollIntoView() til valgt rad (auto i stedet for smooth ved prefers-reduced-motion) - main.css: .shelter-item-badge med #BF360C (= warning_bg på Android, ~5.6:1 mot hvit → WCAG 2.2 AA SC 1.4.3) - i18n en/nb/nn: shelter_outside_nearest_badge Verifisert i Chromium (Playwright): posisjon Oslo, dyplenke /shelter/1681 (Vardø) → tre nærmeste først, fjerde rad med badge, selected og aria-label "Selected (outside nearest). Kaigaten 8 …, 1487.1 km …". Forgejo: #17 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FrDGDmN2WAXvNPS85rWJGK
This commit is contained in:
parent
5365c99bf5
commit
6a50d86266
7 changed files with 98 additions and 10 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
{"id":"tilfluktsrom-7zc","title":"Badge-kontrast under WCAG 2.2 AA — bytt shelter_primary til warning_bg","description":"Mirror av Forgejo-issue #18.\n\nBadge introdusert i 1fb9f14 (#13) bruker hvit fet 11sp tekst på shelter_primary (#FF6B35). Målt kontrast: ~2.84:1.\n\nWCAG 2.2 AA SC 1.4.3:\n- Normaltekst: ≥4.5:1\n- Large text (≥14pt fet eller ≥18pt regular): ≥3.0:1\n\n11sp fet teller som normaltekst → feiler AA-terskelen, og marginalt også 3:1 for large text.\n\nFiks: én linje i app/src/main/res/layout/item_shelter.xml — bytt @color/shelter_primary til @color/warning_bg (#BF360C, dokumentert ~5.5:1 vs hvit i colors.xml-kommentar).\n\nAlternativer som ikke fungerer:\n- shelter_primary_dark (#E55A2B) ~3.6:1 — fortsatt under AA normaltekst\n- Svart tekst på orange — ~7:1, men bryter visuell konsistens\n\nRelevant for offentlig-sektor-godkjenning (Uutilsynet/WAD/EN 301 549).\n\nForgejo: https://kode.naiv.no/olemd/tilfluktsrom/issues/18","acceptance_criteria":"Badge-bakgrunnen på item_shelter.xml outsideNearestBadge er endret til warning_bg. Visuelt verifisert at hvit fet 11sp tekst på den nye bakgrunnen gir ≥4.5:1 målt kontrast (med en kontrast-sjekker eller WCAG-verktøy).","status":"closed","priority":2,"issue_type":"bug","assignee":"Ole-Morten Duesund","owner":"olemd@glemt.net","created_at":"2026-04-29T14:54:00Z","created_by":"Ole-Morten Duesund","updated_at":"2026-04-29T14:55:09Z","started_at":"2026-04-29T14:54:56Z","closed_at":"2026-04-29T14:55:09Z","close_reason":"Bytta @color/shelter_primary til @color/warning_bg på outsideNearestBadge i item_shelter.xml. Kontrast: ~2.84:1 → ~5.6:1 (over WCAG 2.2 AA SC 1.4.3 4.5:1-terskel). Visuell verifisering på enhet/emulator gjenstår.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"id":"tilfluktsrom-7zc","title":"Badge-kontrast under WCAG 2.2 AA — bytt shelter_primary til warning_bg","description":"Mirror av Forgejo-issue #18.\n\nBadge introdusert i 1fb9f14 (#13) bruker hvit fet 11sp tekst på shelter_primary (#FF6B35). Målt kontrast: ~2.84:1.\n\nWCAG 2.2 AA SC 1.4.3:\n- Normaltekst: ≥4.5:1\n- Large text (≥14pt fet eller ≥18pt regular): ≥3.0:1\n\n11sp fet teller som normaltekst → feiler AA-terskelen, og marginalt også 3:1 for large text.\n\nFiks: én linje i app/src/main/res/layout/item_shelter.xml — bytt @color/shelter_primary til @color/warning_bg (#BF360C, dokumentert ~5.5:1 vs hvit i colors.xml-kommentar).\n\nAlternativer som ikke fungerer:\n- shelter_primary_dark (#E55A2B) ~3.6:1 — fortsatt under AA normaltekst\n- Svart tekst på orange — ~7:1, men bryter visuell konsistens\n\nRelevant for offentlig-sektor-godkjenning (Uutilsynet/WAD/EN 301 549).\n\nForgejo: https://kode.naiv.no/olemd/tilfluktsrom/issues/18","acceptance_criteria":"Badge-bakgrunnen på item_shelter.xml outsideNearestBadge er endret til warning_bg. Visuelt verifisert at hvit fet 11sp tekst på den nye bakgrunnen gir ≥4.5:1 målt kontrast (med en kontrast-sjekker eller WCAG-verktøy).","status":"closed","priority":2,"issue_type":"bug","assignee":"Ole-Morten Duesund","owner":"olemd@glemt.net","created_at":"2026-04-29T14:54:00Z","created_by":"Ole-Morten Duesund","updated_at":"2026-04-29T14:55:09Z","started_at":"2026-04-29T14:54:56Z","closed_at":"2026-04-29T14:55:09Z","close_reason":"Bytta @color/shelter_primary til @color/warning_bg på outsideNearestBadge i item_shelter.xml. Kontrast: ~2.84:1 → ~5.6:1 (over WCAG 2.2 AA SC 1.4.3 4.5:1-terskel). Visuell verifisering på enhet/emulator gjenstår.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"id":"tilfluktsrom-efo","title":"PWA: dyplenket tilfluktsrom utenfor topp-N vises som om det er nærmest","description":"Mirror av Forgejo-issue #17.\n\nParallell til Android-fiksen i 1fb9f14 (#13).\n\nNåværende oppførsel: når dyplenken peker på et tilfluktsrom utenfor de N nærmeste, gjør pwa/src/app.ts:266-285 unshift() inn på indeks 0 og setter selectedShelterIndex=0. Resultat: det dyplenkede vises *som om* det er det nærmeste — uten badge, uten separator, uten forklaring. Lista bryter sin egen sort-på-avstand-invariant.\n\nHybrid-fiks (parallell til Android):\n1. Append (push) i stedet for unshift på app.ts:274\n2. Badge i shelter-list.ts på den appendede raden — bruk samme nøkkel shelter_outside_nearest_badge i pwa/src/i18n/{en,nb,nn}.ts\n3. updateList() får outsideNearestRomnr-parameter (eller wrap-objekt parallelt med Android sin ShelterListItem)\n4. Badge-teksten suffikses i aria-label (skjermleser-paritet med Android)\n5. selectedItem.scrollIntoView({block:'nearest', behavior:'smooth'}) etter updateList\n6. Tilgjengelighet: badge ≥4.5:1 kontrast (WCAG 2.2 AA SC 1.4.3). #FF6B35+hvit tekst er ~3.5:1 — *under terskel*. Bruk shelter_primary_dark #E55A2B som badge-bakgrunn, eller svart tekst på orange (~7:1)\n\nUt av skopet: endring av selve dyplenke-formatet (fortsatt /shelter/{romnr}), klyngevisning (#10).\n\nForgejo: https://kode.naiv.no/olemd/tilfluktsrom/issues/17","acceptance_criteria":"Dyplenket tilfluktsrom utenfor topp-N appendes på siste rad i lista (ikke unshift). Den appendede raden har et synlig 'Valgt – utenfor nærområdet'-badge med ≥4.5:1 kontrast. Lista scroller automatisk til valgt rad. Skjermleser leser badge-teksten som del av aria-label. Verifisert manuelt med en deep-link til et tilfluktsrom langt fra brukerens posisjon.","status":"open","priority":2,"issue_type":"bug","owner":"olemd@glemt.net","created_at":"2026-04-29T14:52:08Z","created_by":"Ole-Morten Duesund","updated_at":"2026-04-29T14:52:08Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"id":"tilfluktsrom-efo","title":"PWA: dyplenket tilfluktsrom utenfor topp-N vises som om det er nærmest","description":"Mirror av Forgejo-issue #17.\n\nParallell til Android-fiksen i 1fb9f14 (#13).\n\nNåværende oppførsel: når dyplenken peker på et tilfluktsrom utenfor de N nærmeste, gjør pwa/src/app.ts:266-285 unshift() inn på indeks 0 og setter selectedShelterIndex=0. Resultat: det dyplenkede vises *som om* det er det nærmeste — uten badge, uten separator, uten forklaring. Lista bryter sin egen sort-på-avstand-invariant.\n\nHybrid-fiks (parallell til Android):\n1. Append (push) i stedet for unshift på app.ts:274\n2. Badge i shelter-list.ts på den appendede raden — bruk samme nøkkel shelter_outside_nearest_badge i pwa/src/i18n/{en,nb,nn}.ts\n3. updateList() får outsideNearestRomnr-parameter (eller wrap-objekt parallelt med Android sin ShelterListItem)\n4. Badge-teksten suffikses i aria-label (skjermleser-paritet med Android)\n5. selectedItem.scrollIntoView({block:'nearest', behavior:'smooth'}) etter updateList\n6. Tilgjengelighet: badge ≥4.5:1 kontrast (WCAG 2.2 AA SC 1.4.3). #FF6B35+hvit tekst er ~3.5:1 — *under terskel*. Bruk shelter_primary_dark #E55A2B som badge-bakgrunn, eller svart tekst på orange (~7:1)\n\nUt av skopet: endring av selve dyplenke-formatet (fortsatt /shelter/{romnr}), klyngevisning (#10).\n\nForgejo: https://kode.naiv.no/olemd/tilfluktsrom/issues/17","acceptance_criteria":"Dyplenket tilfluktsrom utenfor topp-N appendes på siste rad i lista (ikke unshift). Den appendede raden har et synlig 'Valgt – utenfor nærområdet'-badge med ≥4.5:1 kontrast. Lista scroller automatisk til valgt rad. Skjermleser leser badge-teksten som del av aria-label. Verifisert manuelt med en deep-link til et tilfluktsrom langt fra brukerens posisjon.","status":"closed","priority":2,"issue_type":"bug","assignee":"Ole-Morten Duesund","owner":"olemd@glemt.net","created_at":"2026-04-29T14:52:08Z","created_by":"Ole-Morten Duesund","updated_at":"2026-08-17T10:48:32Z","started_at":"2026-08-17T10:42:26Z","closed_at":"2026-08-17T10:48:32Z","close_reason":"Fikset: push i stedet for unshift, badge (#BF360C, ~5.6:1) + aria-prefiks + scrollIntoView i shelter-list.ts. Verifisert i Chromium via Playwright med dyplenke /shelter/1681 fra Oslo-posisjon.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"id":"tilfluktsrom-5vc","title":"Brukerens retningspil i kartet er hvit og forsvinner på lyse underlag","description":"Mirror av Forgejo-issue #16.\n\nNår brukeren beveger seg raskt nok til at OSMDroid bytter fra person- til retningspil-ikonet i MyLocationNewOverlay, blir pilen tilnærmet usynlig: heltrukken hvit uten kontur eller skygge. På lyse tiles (snø, sand, brede veifyll, lyse OSM-temaer) forsvinner den helt.\n\nÅrsak: MainActivity.kt:190 instansierer MyLocationNewOverlay uten å overstyre setDirectionIcon()/setPersonIcon() — OSMDroid bruker da stock hvite bitmaps.\n\nForslag:\n- Egen vector i res/drawable/ic_user_direction.xml med kontrastfarget fyll + motsatt-farge kontur\n- Kall myLocationOverlay.setDirectionIcon(...) + setPersonIcon(...) eksplisitt\n- Vurder halo-ring (blå pil i hvit ring, Google Maps-stil) for variert underlag\n- Sjekk PWA-en (Leaflet) for samme problem\n\nTilgjengelighet:\n- ≥3:1 kontrast mot både lyse og mørke tiles (WCAG 2.2 AA, ikke-tekstlig innhold)\n- Ikke avhengig av farge alene; konturen sikrer at formen faktisk synes\n\nForgejo: https://kode.naiv.no/olemd/tilfluktsrom/issues/16","acceptance_criteria":"Pilen og person-ikonet i MyLocationNewOverlay er tydelig synlig mot både lyse og mørke kartunderlag (≥3:1 kontrast). Verifisert manuelt på minst ett lyst og ett mørkt kartområde, både stillestående (person) og i bevegelse (pil).","status":"closed","priority":2,"issue_type":"bug","assignee":"Ole-Morten Duesund","owner":"olemd@glemt.net","created_at":"2026-04-29T14:06:57Z","created_by":"Ole-Morten Duesund","updated_at":"2026-04-29T14:15:13Z","started_at":"2026-04-29T14:09:43Z","closed_at":"2026-04-29T14:15:13Z","close_reason":"Fikset i d2291a2 — egne ikoner i res/drawable/ic_user_dot.xml og ic_user_arrow.xml, satt på MyLocationNewOverlay med setPersonIcon/setDirectionIcon + (0.5, 0.5)-anker. Visuell verifisering på enhet/emulator gjenstår.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"id":"tilfluktsrom-5vc","title":"Brukerens retningspil i kartet er hvit og forsvinner på lyse underlag","description":"Mirror av Forgejo-issue #16.\n\nNår brukeren beveger seg raskt nok til at OSMDroid bytter fra person- til retningspil-ikonet i MyLocationNewOverlay, blir pilen tilnærmet usynlig: heltrukken hvit uten kontur eller skygge. På lyse tiles (snø, sand, brede veifyll, lyse OSM-temaer) forsvinner den helt.\n\nÅrsak: MainActivity.kt:190 instansierer MyLocationNewOverlay uten å overstyre setDirectionIcon()/setPersonIcon() — OSMDroid bruker da stock hvite bitmaps.\n\nForslag:\n- Egen vector i res/drawable/ic_user_direction.xml med kontrastfarget fyll + motsatt-farge kontur\n- Kall myLocationOverlay.setDirectionIcon(...) + setPersonIcon(...) eksplisitt\n- Vurder halo-ring (blå pil i hvit ring, Google Maps-stil) for variert underlag\n- Sjekk PWA-en (Leaflet) for samme problem\n\nTilgjengelighet:\n- ≥3:1 kontrast mot både lyse og mørke tiles (WCAG 2.2 AA, ikke-tekstlig innhold)\n- Ikke avhengig av farge alene; konturen sikrer at formen faktisk synes\n\nForgejo: https://kode.naiv.no/olemd/tilfluktsrom/issues/16","acceptance_criteria":"Pilen og person-ikonet i MyLocationNewOverlay er tydelig synlig mot både lyse og mørke kartunderlag (≥3:1 kontrast). Verifisert manuelt på minst ett lyst og ett mørkt kartområde, både stillestående (person) og i bevegelse (pil).","status":"closed","priority":2,"issue_type":"bug","assignee":"Ole-Morten Duesund","owner":"olemd@glemt.net","created_at":"2026-04-29T14:06:57Z","created_by":"Ole-Morten Duesund","updated_at":"2026-04-29T14:15:13Z","started_at":"2026-04-29T14:09:43Z","closed_at":"2026-04-29T14:15:13Z","close_reason":"Fikset i d2291a2 — egne ikoner i res/drawable/ic_user_dot.xml og ic_user_arrow.xml, satt på MyLocationNewOverlay med setPersonIcon/setDirectionIcon + (0.5, 0.5)-anker. Visuell verifisering på enhet/emulator gjenstår.","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"id":"tilfluktsrom-5s7","title":"PWA: manuell testing mangler","description":"Mirror av Forgejo-issue #1.\n\nPWA-versjonen (pwa/) er skrevet, men ikke manuelt testet i nettleser. Enhetstestene passerer, men appen må verifiseres i praksis:\n\n- Start utviklingsserver og test i Chrome/Firefox\n- Test offline-modus (service worker)\n- Test kompass (iOS Safari + Android Chrome)\n- Test installasjon via «Legg til på startskjerm»\n- Test kartbufring og offline kartvisning\n- Test på fysisk iPhone (iOS-spesifikk kompasshåndtering)\n- Test i18n (norsk bokmål, nynorsk, engelsk)\n\nForgejo: https://kode.naiv.no/olemd/tilfluktsrom/issues/1","status":"closed","priority":2,"issue_type":"task","owner":"olemd@glemt.net","created_at":"2026-04-29T13:57:04Z","created_by":"Ole-Morten Duesund","updated_at":"2026-04-29T14:02:57Z","closed_at":"2026-04-29T14:02:57Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"id":"tilfluktsrom-5s7","title":"PWA: manuell testing mangler","description":"Mirror av Forgejo-issue #1.\n\nPWA-versjonen (pwa/) er skrevet, men ikke manuelt testet i nettleser. Enhetstestene passerer, men appen må verifiseres i praksis:\n\n- Start utviklingsserver og test i Chrome/Firefox\n- Test offline-modus (service worker)\n- Test kompass (iOS Safari + Android Chrome)\n- Test installasjon via «Legg til på startskjerm»\n- Test kartbufring og offline kartvisning\n- Test på fysisk iPhone (iOS-spesifikk kompasshåndtering)\n- Test i18n (norsk bokmål, nynorsk, engelsk)\n\nForgejo: https://kode.naiv.no/olemd/tilfluktsrom/issues/1","status":"closed","priority":2,"issue_type":"task","owner":"olemd@glemt.net","created_at":"2026-04-29T13:57:04Z","created_by":"Ole-Morten Duesund","updated_at":"2026-04-29T14:02:57Z","closed_at":"2026-04-29T14:02:57Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"id":"tilfluktsrom-nt8","title":"Åpne i kartapp for gangvei til tilfluktsrom","description":"Mirror av Forgejo-issue #2.\n\nLegg til knapp (på bunnark og/eller kompassvisning) som åpner gangveibeskrivelse til valgt tilfluktsrom i ekstern kartapp.\n\nImplementasjon:\n- ACTION_VIEW intent med geo: URI: geo:lat,lon?q=lat,lon(Tilfluktsrom - adresse)\n- geo: håndteres av tilgjengelig kartapp (OsmAnd, Organic Maps, Google Maps, ...)\n- OsmAnd og Organic Maps støtter offline-navigasjon med geo: — ideelt for degradert nett\n- IKKE hardkode Google Maps-URL-er — bruk geo:\n- Faller pent tilbake hvis ingen kartapp er installert (Toast med koordinater å kopiere)\n- Knapp ved siden av tilfluktsrom-adresse i bunnarket\n\nI en akuttsituasjon er det å finne tilfluktsrommet på kartet bare halve problemet — du må vite gangveien dit. geo:-intent fungerer med offline-kapable kartapper, kritisk når nettet er nede.\n\nForgejo: https://kode.naiv.no/olemd/tilfluktsrom/issues/2","status":"open","priority":2,"issue_type":"feature","owner":"olemd@glemt.net","created_at":"2026-04-29T13:57:03Z","created_by":"Ole-Morten Duesund","updated_at":"2026-04-29T13:57:03Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"id":"tilfluktsrom-nt8","title":"Åpne i kartapp for gangvei til tilfluktsrom","description":"Mirror av Forgejo-issue #2.\n\nLegg til knapp (på bunnark og/eller kompassvisning) som åpner gangveibeskrivelse til valgt tilfluktsrom i ekstern kartapp.\n\nImplementasjon:\n- ACTION_VIEW intent med geo: URI: geo:lat,lon?q=lat,lon(Tilfluktsrom - adresse)\n- geo: håndteres av tilgjengelig kartapp (OsmAnd, Organic Maps, Google Maps, ...)\n- OsmAnd og Organic Maps støtter offline-navigasjon med geo: — ideelt for degradert nett\n- IKKE hardkode Google Maps-URL-er — bruk geo:\n- Faller pent tilbake hvis ingen kartapp er installert (Toast med koordinater å kopiere)\n- Knapp ved siden av tilfluktsrom-adresse i bunnarket\n\nI en akuttsituasjon er det å finne tilfluktsrommet på kartet bare halve problemet — du må vite gangveien dit. geo:-intent fungerer med offline-kapable kartapper, kritisk når nettet er nede.\n\nForgejo: https://kode.naiv.no/olemd/tilfluktsrom/issues/2","status":"open","priority":2,"issue_type":"feature","owner":"olemd@glemt.net","created_at":"2026-04-29T13:57:03Z","created_by":"Ole-Morten Duesund","updated_at":"2026-04-29T13:57:03Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,11 @@ const NEAREST_COUNT = 3;
|
||||||
let allShelters: Shelter[] = [];
|
let allShelters: Shelter[] = [];
|
||||||
let nearestShelters: ShelterWithDistance[] = [];
|
let nearestShelters: ShelterWithDistance[] = [];
|
||||||
let selectedShelterIndex = 0;
|
let selectedShelterIndex = 0;
|
||||||
|
// romnr of a user-selected shelter that has been appended to the list because
|
||||||
|
// it is outside the geographic top-N (deep link, marker tap). null when the
|
||||||
|
// selection is a genuine top-N entry. Drives the "outside nearest" badge in
|
||||||
|
// the list — mirrors ShelterListItem.isOutsideNearest in the Android app.
|
||||||
|
let outsideNearestRomnr: number | null = null;
|
||||||
let currentLocation: LatLon | null = null;
|
let currentLocation: LatLon | null = null;
|
||||||
let deviceHeading = 0;
|
let deviceHeading = 0;
|
||||||
let isCompassMode = false;
|
let isCompassMode = false;
|
||||||
|
|
@ -248,6 +253,15 @@ function startLocationUpdates(): void {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Re-render the bottom-sheet list from current module state. */
|
||||||
|
function renderShelterList(): void {
|
||||||
|
shelterList.updateList(
|
||||||
|
nearestShelters,
|
||||||
|
selectedShelterIndex,
|
||||||
|
outsideNearestRomnr,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function updateNearestShelters(location: LatLon): void {
|
function updateNearestShelters(location: LatLon): void {
|
||||||
if (allShelters.length === 0) return;
|
if (allShelters.length === 0) return;
|
||||||
|
|
||||||
|
|
@ -268,10 +282,14 @@ function updateNearestShelters(location: LatLon): void {
|
||||||
);
|
);
|
||||||
if (inList >= 0) {
|
if (inList >= 0) {
|
||||||
selectedShelterIndex = inList;
|
selectedShelterIndex = inList;
|
||||||
|
outsideNearestRomnr = null;
|
||||||
} else {
|
} else {
|
||||||
const shelter = allShelters.find((s) => s.romnr === selectedRomnr);
|
const shelter = allShelters.find((s) => s.romnr === selectedRomnr);
|
||||||
if (shelter) {
|
if (shelter) {
|
||||||
nearestShelters.unshift({
|
// Append (not prepend) so the list keeps its sorted-by-distance
|
||||||
|
// invariant; the badge explains why this row is here. Same hybrid
|
||||||
|
// approach as MainActivity.rebuildShelterList() on Android.
|
||||||
|
nearestShelters.push({
|
||||||
shelter,
|
shelter,
|
||||||
distanceMeters: distanceMeters(
|
distanceMeters: distanceMeters(
|
||||||
location.latitude, location.longitude,
|
location.latitude, location.longitude,
|
||||||
|
|
@ -282,20 +300,23 @@ function updateNearestShelters(location: LatLon): void {
|
||||||
shelter.latitude, shelter.longitude,
|
shelter.latitude, shelter.longitude,
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
selectedShelterIndex = 0;
|
selectedShelterIndex = nearestShelters.length - 1;
|
||||||
|
outsideNearestRomnr = shelter.romnr;
|
||||||
} else {
|
} else {
|
||||||
// Selected shelter no longer exists in the dataset (e.g. DSB
|
// Selected shelter no longer exists in the dataset (e.g. DSB
|
||||||
// decommissioned it). Fall back to nearest.
|
// decommissioned it). Fall back to nearest.
|
||||||
selectedRomnr = null;
|
selectedRomnr = null;
|
||||||
userSelectedShelter = false;
|
userSelectedShelter = false;
|
||||||
selectedShelterIndex = 0;
|
selectedShelterIndex = 0;
|
||||||
|
outsideNearestRomnr = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
selectedShelterIndex = 0;
|
selectedShelterIndex = 0;
|
||||||
|
outsideNearestRomnr = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
shelterList.updateList(nearestShelters, selectedShelterIndex);
|
renderShelterList();
|
||||||
updateSelectedShelter(false);
|
updateSelectedShelter(false);
|
||||||
|
|
||||||
statusBar.setStatus(t('status_shelters_loaded', allShelters.length));
|
statusBar.setStatus(t('status_shelters_loaded', allShelters.length));
|
||||||
|
|
@ -337,7 +358,7 @@ function updateSelectedShelter(isUserAction: boolean): void {
|
||||||
compassView.setNorthAngle(-deviceHeading);
|
compassView.setNorthAngle(-deviceHeading);
|
||||||
|
|
||||||
// Update shelter list selection
|
// Update shelter list selection
|
||||||
shelterList.updateList(nearestShelters, selectedShelterIndex);
|
renderShelterList();
|
||||||
|
|
||||||
// Update map: highlight selected and optionally fit view
|
// Update map: highlight selected and optionally fit view
|
||||||
if (isUserAction) {
|
if (isUserAction) {
|
||||||
|
|
@ -503,13 +524,15 @@ function selectShelterByData(shelter: Shelter): void {
|
||||||
)
|
)
|
||||||
: NaN;
|
: NaN;
|
||||||
|
|
||||||
nearestShelters.unshift({
|
// Append rather than prepend — see updateNearestShelters().
|
||||||
|
nearestShelters.push({
|
||||||
shelter,
|
shelter,
|
||||||
distanceMeters: dist,
|
distanceMeters: dist,
|
||||||
bearingDegrees: bearing,
|
bearingDegrees: bearing,
|
||||||
});
|
});
|
||||||
selectedShelterIndex = 0;
|
selectedShelterIndex = nearestShelters.length - 1;
|
||||||
shelterList.updateList(nearestShelters, selectedShelterIndex);
|
outsideNearestRomnr = shelter.romnr;
|
||||||
|
renderShelterList();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectedShelter(true);
|
updateSelectedShelter(true);
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ export const en: Record<string, string> = {
|
||||||
// Shelter info
|
// Shelter info
|
||||||
shelter_capacity: '%d places',
|
shelter_capacity: '%d places',
|
||||||
shelter_room_nr: 'Room %d',
|
shelter_room_nr: 'Room %d',
|
||||||
|
shelter_outside_nearest_badge: 'Selected (outside nearest)',
|
||||||
nearest_shelter: 'Nearest shelter',
|
nearest_shelter: 'Nearest shelter',
|
||||||
no_shelters: 'No shelter data available',
|
no_shelters: 'No shelter data available',
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export const nb: Record<string, string> = {
|
||||||
|
|
||||||
shelter_capacity: '%d plasser',
|
shelter_capacity: '%d plasser',
|
||||||
shelter_room_nr: 'Rom %d',
|
shelter_room_nr: 'Rom %d',
|
||||||
|
shelter_outside_nearest_badge: 'Valgt – utenfor nærområdet',
|
||||||
nearest_shelter: 'Nærmeste tilfluktsrom',
|
nearest_shelter: 'Nærmeste tilfluktsrom',
|
||||||
no_shelters: 'Ingen tilfluktsromdata tilgjengelig',
|
no_shelters: 'Ingen tilfluktsromdata tilgjengelig',
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export const nn: Record<string, string> = {
|
||||||
|
|
||||||
shelter_capacity: '%d plassar',
|
shelter_capacity: '%d plassar',
|
||||||
shelter_room_nr: 'Rom %d',
|
shelter_room_nr: 'Rom %d',
|
||||||
|
shelter_outside_nearest_badge: 'Vald – utanfor nærområdet',
|
||||||
nearest_shelter: 'Næraste tilfluktsrom',
|
nearest_shelter: 'Næraste tilfluktsrom',
|
||||||
no_shelters: 'Ingen tilfluktsromdata tilgjengeleg',
|
no_shelters: 'Ingen tilfluktsromdata tilgjengeleg',
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -355,6 +355,24 @@ html, body {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Selected (outside nearest)" badge on a row appended below the top-N.
|
||||||
|
* #BF360C = warning_bg in the Android colors.xml: ~5.6:1 against white,
|
||||||
|
* clearing WCAG 2.2 AA SC 1.4.3 (4.5:1) for this small bold text. The plain
|
||||||
|
* shelter orange (#FF6B35) only reaches ~2.8:1 and must not be used here.
|
||||||
|
*/
|
||||||
|
.shelter-item-badge {
|
||||||
|
align-self: flex-start;
|
||||||
|
background: #BF360C;
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 1px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Loading overlay --- */
|
/* --- Loading overlay --- */
|
||||||
#loading-overlay {
|
#loading-overlay {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,19 @@ export function initShelterList(
|
||||||
onSelect = onShelterSelect;
|
onSelect = onShelterSelect;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Render the list of nearest shelters using safe DOM methods. */
|
/**
|
||||||
|
* Render the list of nearest shelters using safe DOM methods.
|
||||||
|
*
|
||||||
|
* @param outsideNearestRomnr romnr of a row that was appended because the
|
||||||
|
* user selected it (deep link / marker tap) even though it is outside the
|
||||||
|
* geographic top-N. That row gets a visible badge and a screen-reader
|
||||||
|
* prefix so the list's sort-by-distance invariant is explained rather than
|
||||||
|
* silently broken. null when every row is a genuine top-N entry.
|
||||||
|
*/
|
||||||
export function updateList(
|
export function updateList(
|
||||||
shelters: ShelterWithDistance[],
|
shelters: ShelterWithDistance[],
|
||||||
currentSelectedIndex: number,
|
currentSelectedIndex: number,
|
||||||
|
outsideNearestRomnr: number | null = null,
|
||||||
): void {
|
): void {
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
selectedIndex = currentSelectedIndex;
|
selectedIndex = currentSelectedIndex;
|
||||||
|
|
@ -46,7 +55,26 @@ export function updateList(
|
||||||
t('shelter_room_nr', swd.shelter.romnr),
|
t('shelter_room_nr', swd.shelter.romnr),
|
||||||
].join(' \u00B7 ');
|
].join(' \u00B7 ');
|
||||||
|
|
||||||
item.setAttribute('aria-label', `${swd.shelter.adresse}, ${details}`);
|
const isOutsideNearest =
|
||||||
|
outsideNearestRomnr !== null && swd.shelter.romnr === outsideNearestRomnr;
|
||||||
|
|
||||||
|
// Prefix (not suffix) the badge text so screen-reader users hear the
|
||||||
|
// caveat before the address — same order as contentDescription in
|
||||||
|
// ShelterListAdapter.kt on Android.
|
||||||
|
const baseLabel = `${swd.shelter.adresse}, ${details}`;
|
||||||
|
item.setAttribute(
|
||||||
|
'aria-label',
|
||||||
|
isOutsideNearest
|
||||||
|
? `${t('shelter_outside_nearest_badge')}. ${baseLabel}`
|
||||||
|
: baseLabel,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isOutsideNearest) {
|
||||||
|
const badge = document.createElement('span');
|
||||||
|
badge.className = 'shelter-item-badge';
|
||||||
|
badge.textContent = t('shelter_outside_nearest_badge');
|
||||||
|
item.appendChild(badge);
|
||||||
|
}
|
||||||
|
|
||||||
const addressSpan = document.createElement('span');
|
const addressSpan = document.createElement('span');
|
||||||
addressSpan.className = 'shelter-item-address';
|
addressSpan.className = 'shelter-item-address';
|
||||||
|
|
@ -64,4 +92,20 @@ export function updateList(
|
||||||
});
|
});
|
||||||
container!.appendChild(item);
|
container!.appendChild(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Keep the selected row visible — matters when the selection was appended
|
||||||
|
// below the fold, or when the user scrolled the list away from it.
|
||||||
|
const selectedEl = container.children[selectedIndex] as HTMLElement | undefined;
|
||||||
|
selectedEl?.scrollIntoView?.({
|
||||||
|
block: 'nearest',
|
||||||
|
behavior: prefersReducedMotion() ? 'auto' : 'smooth',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function prefersReducedMotion(): boolean {
|
||||||
|
return (
|
||||||
|
typeof window !== 'undefined' &&
|
||||||
|
typeof window.matchMedia === 'function' &&
|
||||||
|
window.matchMedia('(prefers-reduced-motion: reduce)').matches
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue