feat(windkarte): Legend-Toggle — Status ein-/ausblendbar per Klick
Deploy / verify (push) Successful in 54s
Deploy / deploy (push) Successful in 1m1s

Klick auf Legendenitem blendet den Status aus (opacity 0, kein Klick,
kein Hover). Panel schließt sich wenn der Status des gewählten Gebiets
ausgeblendet wird. Visuell: opacity + line-through auf versteckten Items.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-05-13 15:09:14 +02:00
parent 7d4f52ba02
commit 0f6a58fed4
2 changed files with 59 additions and 15 deletions
+34 -2
View File
@@ -8,13 +8,37 @@
onselect, onselect,
onready = null, onready = null,
initialGebietsNr = null, initialGebietsNr = null,
hiddenStatuses = [],
}: { }: {
areas: WindArea[]; areas: WindArea[];
onselect: (area: WindArea | null, center: [number, number] | null) => void; onselect: (area: WindArea | null, center: [number, number] | null) => void;
onready?: ((ctrl: { reset: () => void }) => void) | null; onready?: ((ctrl: { reset: () => void }) => void) | null;
initialGebietsNr?: string | null; initialGebietsNr?: string | null;
hiddenStatuses?: string[];
} = $props(); } = $props();
// Mirror prop into plain variable so Leaflet event handlers read current value.
let _hidden: string[] = [];
$effect(() => {
_hidden = hiddenStatuses;
if (!geojsonLayer) return;
restyleAll(selectedSlug);
// Deselect if selected area's status was just hidden.
if (selectedSlug) {
geojsonLayer.eachLayer((l) => {
const f = (l as import("leaflet").Path & { feature?: GeoJSON.Feature }).feature;
if (f?.properties?.name === selectedSlug) {
const s = f.properties?.status ?? "rechtskraeftig";
if (_hidden.includes(s)) {
selectedSlug = null;
onselect(null, null);
bufferLayer?.clearLayers();
}
}
});
}
});
let mapEl: HTMLDivElement; let mapEl: HTMLDivElement;
let map: import("leaflet").Map | null = null; let map: import("leaflet").Map | null = null;
let bufferLayer: import("leaflet").LayerGroup | null = null; let bufferLayer: import("leaflet").LayerGroup | null = null;
@@ -46,7 +70,11 @@
const f = (l as import("leaflet").Path & { feature?: GeoJSON.Feature }).feature; const f = (l as import("leaflet").Path & { feature?: GeoJSON.Feature }).feature;
if (!f) return; if (!f) return;
const s = f.properties?.status ?? "rechtskraeftig"; const s = f.properties?.status ?? "rechtskraeftig";
if (_hidden.includes(s)) {
(l as import("leaflet").Path).setStyle({ opacity: 0, fillOpacity: 0, weight: 0 });
} else {
(l as import("leaflet").Path).setStyle(style(s, f.properties?.name === sel)); (l as import("leaflet").Path).setStyle(style(s, f.properties?.name === sel));
}
}); });
} }
@@ -168,19 +196,23 @@
// Hover highlight // Hover highlight
layer.on("mouseover", () => { layer.on("mouseover", () => {
if (feature.properties?.name !== selectedSlug) {
const s = feature.properties?.status ?? "rechtskraeftig"; const s = feature.properties?.status ?? "rechtskraeftig";
if (_hidden.includes(s)) return;
if (feature.properties?.name !== selectedSlug) {
(layer as import("leaflet").Path).setStyle(style(s, false, true)); (layer as import("leaflet").Path).setStyle(style(s, false, true));
} }
}); });
layer.on("mouseout", () => { layer.on("mouseout", () => {
if (feature.properties?.name !== selectedSlug) {
const s = feature.properties?.status ?? "rechtskraeftig"; const s = feature.properties?.status ?? "rechtskraeftig";
if (_hidden.includes(s)) return;
if (feature.properties?.name !== selectedSlug) {
(layer as import("leaflet").Path).setStyle(style(s, false, false)); (layer as import("leaflet").Path).setStyle(style(s, false, false));
} }
}); });
layer.on("click", async (e) => { layer.on("click", async (e) => {
const s = feature.properties?.status ?? "rechtskraeftig";
if (_hidden.includes(s)) return;
L.DomEvent.stopPropagation(e); L.DomEvent.stopPropagation(e);
await selectFeature(L, feature, layer, area); await selectFeature(L, feature, layer, area);
}); });
+24 -12
View File
@@ -21,6 +21,13 @@ let areas = $state<WindArea[]>([]);
let mapReady = $state(false); let mapReady = $state(false);
let initialGebietsNr = $state<string | null>(null); let initialGebietsNr = $state<string | null>(null);
let resetMap: (() => void) | null = null; let resetMap: (() => void) | null = null;
let hiddenStatuses = $state<string[]>([]);
function toggleStatus(status: string) {
hiddenStatuses = hiddenStatuses.includes(status)
? hiddenStatuses.filter((s) => s !== status)
: [...hiddenStatuses, status];
}
const MapComponent = browser const MapComponent = browser
? import("$lib/components/WindkarteMap.svelte").then((m) => m.default) ? import("$lib/components/WindkarteMap.svelte").then((m) => m.default)
@@ -105,6 +112,7 @@ let areas = $state<WindArea[]>([]);
<Map <Map
{areas} {areas}
{initialGebietsNr} {initialGebietsNr}
{hiddenStatuses}
onselect={handleSelect} onselect={handleSelect}
onready={(ctrl) => { resetMap = ctrl.reset; }} onready={(ctrl) => { resetMap = ctrl.reset; }}
/> />
@@ -127,18 +135,22 @@ let areas = $state<WindArea[]>([]);
<!-- Legend --> <!-- Legend -->
<div class="mt-2 flex flex-wrap gap-x-5 gap-y-1.5 text-xs text-stein-400"> <div class="mt-2 flex flex-wrap gap-x-5 gap-y-1.5 text-xs text-stein-400">
<span class="flex items-center gap-1.5"> {#each [
<span class="inline-block size-2.5 rounded-sm" style="background:#2d7a45; opacity:.7"></span> { status: "rechtskraeftig", color: "#2d7a45", label: t(T.windkarte_legend_rechtskraeftig) },
{t(T.windkarte_legend_rechtskraeftig)} { status: "entwurf_2", color: "#b08a52", label: t(T.windkarte_legend_entwurf_2) },
</span> { status: "entwurf_2_voraussichtlich", color: "#d4752a", label: t(T.windkarte_legend_entwurf_2_voraussichtlich) },
<span class="flex items-center gap-1.5"> ] as item}
<span class="inline-block size-2.5 rounded-sm" style="background:#b08a52; opacity:.7"></span> <button
{t(T.windkarte_legend_entwurf_2)} type="button"
</span> onclick={() => toggleStatus(item.status)}
<span class="flex items-center gap-1.5"> class="flex items-center gap-1.5 transition-opacity cursor-pointer select-none
<span class="inline-block size-2.5 rounded-sm" style="background:#d4752a; opacity:.7"></span> {hiddenStatuses.includes(item.status) ? 'opacity-35' : 'opacity-100'}"
{t(T.windkarte_legend_entwurf_2_voraussichtlich)} title={hiddenStatuses.includes(item.status) ? 'Einblenden' : 'Ausblenden'}
</span> >
<span class="inline-block size-2.5 rounded-sm shrink-0" style="background:{item.color}; opacity:.7"></span>
<span class="{hiddenStatuses.includes(item.status) ? 'line-through' : ''}">{item.label}</span>
</button>
{/each}
<span class="flex items-center gap-x-3 gap-y-1 flex-wrap text-stein-300"> <span class="flex items-center gap-x-3 gap-y-1 flex-wrap text-stein-300">
<span class="text-stein-400">{t(T.windkarte_legend_distances)}</span> <span class="text-stein-400">{t(T.windkarte_legend_distances)}</span>
{#each [{ color: "#e35651", label: "200 m" }, { color: "#b08a52", label: "600 m" }, { color: "#436e85", label: "1 km" }] as ring} {#each [{ color: "#e35651", label: "200 m" }, { color: "#b08a52", label: "600 m" }, { color: "#436e85", label: "1 km" }] as ring}