aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/components/ParkMap.astro
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/ParkMap.astro')
-rw-r--r--src/components/ParkMap.astro159
1 files changed, 134 insertions, 25 deletions
diff --git a/src/components/ParkMap.astro b/src/components/ParkMap.astro
index 6c10d93..07d54bb 100644
--- a/src/components/ParkMap.astro
+++ b/src/components/ParkMap.astro
@@ -84,6 +84,71 @@
:global(.park-map-filter-body .parks-disclaimer) {
margin: 0.3rem 0 0;
}
+ :global(.park-map-filter-status) {
+ display: inline-block;
+ margin-left: 0.3rem;
+ padding: 0 0.35rem;
+ border-radius: 999px;
+ background: #1b5e20;
+ color: #fff;
+ font-size: 0.7rem;
+ font-weight: 700;
+ }
+ :global(.park-map-filter-footer) {
+ flex: none;
+ display: flex;
+ gap: 0.5rem;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0.35rem 0.6rem;
+ border-top: 1px solid var(--sl-color-gray-5, #cbd5e1);
+ font-size: 0.72rem;
+ color: var(--sl-color-gray-2, #666);
+ }
+ :global(.park-map-filter-close) {
+ flex: none;
+ padding: 0.25rem 0.8rem;
+ border: 1px solid var(--sl-color-gray-5, #cbd5e1);
+ border-radius: 999px;
+ background: transparent;
+ color: inherit;
+ font: inherit;
+ font-size: 0.78rem;
+ cursor: pointer;
+ }
+ :global(.park-name-marker) {
+ background: none;
+ border: 0;
+ }
+ :global(.park-name-label) {
+ position: absolute;
+ left: 0;
+ top: 0;
+ transform: translate(-50%, -170%);
+ background: rgba(255, 255, 255, 0.92);
+ color: #1b5e20;
+ font-size: 11px;
+ font-weight: 700;
+ line-height: 1.2;
+ padding: 1px 4px;
+ border-radius: 3px;
+ white-space: nowrap;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
+ pointer-events: none;
+ }
+ :global(.park-facility-tip) {
+ background: rgba(255, 255, 255, 0.96);
+ color: #4a148c;
+ border: 1px solid #ce93d8;
+ border-radius: 4px;
+ font-size: 11px;
+ font-weight: 700;
+ padding: 1px 4px;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+ }
+ :global(.park-facility-tip.leaflet-tooltip-top::before) {
+ display: none;
+ }
:global(.park-map.park-map-touch.leaflet-container) {
touch-action: none;
}
@@ -324,6 +389,7 @@
background: #0277bd;
border: 1px solid #01579b;
border-radius: 50%;
+ font-size: 12px;
}
:global(.park-fac-clock) {
background: #f9a825;
@@ -664,52 +730,51 @@
const filterToggle = document.createElement("button");
filterToggle.type = "button";
filterToggle.className = "park-map-filter-toggle";
- const filterToggleLabel = (open: boolean) =>
- `絞り込み <span aria-hidden="true">${open ? "▴" : "▾"}</span>`;
- filterToggle.innerHTML = filterToggleLabel(false);
+ filterToggle.innerHTML =
+ '絞り込み <span class="park-map-filter-status" hidden>適用中</span> <span class="park-map-filter-caret" aria-hidden="true">▾</span>';
+ const filterStatus = filterToggle.querySelector<HTMLElement>(".park-map-filter-status");
+ const filterCaret = filterToggle.querySelector<HTMLElement>(".park-map-filter-caret");
const filterBody = document.createElement("div");
filterBody.className = "park-map-filter-body";
filterBody.hidden = true;
- filterPanel.append(filterToggle, filterBody);
+ const filterFooter = document.createElement("div");
+ filterFooter.className = "park-map-filter-footer";
+ filterFooter.innerHTML = "<span>情報には誤りが含まれる場合があります。</span>";
+ const filterClose = document.createElement("button");
+ filterClose.type = "button";
+ filterClose.className = "park-map-filter-close";
+ filterClose.textContent = "閉じる";
+ filterClose.addEventListener("click", () => {
+ document
+ .querySelector<HTMLButtonElement>(".park-map-fullscreen button")
+ ?.click();
+ });
+ filterFooter.appendChild(filterClose);
+ filterPanel.append(filterToggle, filterBody, filterFooter);
L.DomEvent.disableClickPropagation(filterPanel);
L.DomEvent.disableScrollPropagation(filterPanel);
filterToggle.addEventListener("click", () => {
filterBody.hidden = !filterBody.hidden;
- filterToggle.innerHTML = filterToggleLabel(!filterBody.hidden);
+ if (filterCaret) filterCaret.textContent = filterBody.hidden ? "▾" : "▴";
});
- let filterHome: {
- parent: Node;
- next: Node | null;
- disclaimerParent: Node | null;
- disclaimerNext: Node | null;
- } | null = null;
+ let filterHome: { parent: Node; next: Node | null } | null = null;
const setFiltersInMap = (on: boolean) => {
const controls = document.querySelector<HTMLElement>(".parks-controls");
- const disclaimer = document.querySelector<HTMLElement>(".parks-disclaimer");
if (on) {
if (!controls || filterHome) return;
filterHome = {
parent: controls.parentNode as Node,
next: controls.nextSibling,
- disclaimerParent: disclaimer?.parentNode ?? null,
- disclaimerNext: disclaimer?.nextSibling ?? null,
};
filterBody.append(controls);
- if (disclaimer) filterBody.append(disclaimer);
map.getContainer().appendChild(filterPanel);
filterPanel.hidden = false;
filterBody.hidden = false;
- filterToggle.innerHTML = filterToggleLabel(true);
+ if (filterCaret) filterCaret.textContent = "▴";
} else if (filterHome && controls) {
filterHome.parent.insertBefore(controls, filterHome.next);
- if (disclaimer && filterHome.disclaimerParent) {
- filterHome.disclaimerParent.insertBefore(
- disclaimer,
- filterHome.disclaimerNext,
- );
- }
filterPanel.remove();
filterPanel.hidden = true;
filterHome = null;
@@ -741,6 +806,7 @@
else map.dragging.disable();
}
window.setTimeout(() => map.invalidateSize(), 60);
+ window.setTimeout(() => map.invalidateSize(), 350);
};
const enterCssMax = () => {
target.classList.add("is-maximized");
@@ -1017,7 +1083,10 @@
lyr.on("click", (ev: any) => {
if (ev?.originalEvent) L.DomEvent.stopPropagation(ev.originalEvent);
if (!name) return;
- filterName(name, false);
+ highlightOnly(name);
+ document.dispatchEvent(
+ new CustomEvent("park:select-row", { detail: { name } }),
+ );
showSelected(name, name, true, city || "市外", nearbyMapsQuery(name, city));
});
},
@@ -1061,6 +1130,14 @@
`<strong>${icon} ${f.name}</strong>${nearby ? "(市外)" : ""}<br>${f.type}<br>${f.hours || ""}${f.closed ? `<br>休: ${f.closed}` : ""}<br>トイレ: ${f.toilet || "未確認"} / 給水: ${f.water || "未確認"}`,
)
.addTo(publicFacilityLayer);
+ const rawName = String(f.name || "");
+ const shortName =
+ rawName.length > 12 ? `${rawName.slice(0, 11)}…` : rawName;
+ marker.bindTooltip(shortName, {
+ direction: "top",
+ offset: [0, -12],
+ className: "park-facility-tip",
+ });
facilityMarkers.push({ name: String(f.name || ""), layer: marker });
});
};
@@ -1103,13 +1180,13 @@
}
if ((p["水飲み場"] || "").trim()) {
icons.push(
- '<span class="park-fac park-fac-water" title="水飲み場あり" aria-label="水飲み場あり"><svg viewBox="0 0 24 24" width="12" height="12" aria-hidden="true"><path fill="currentColor" d="M12 2.7c-3.4 4.2-6 8.1-6 11.3a6 6 0 0 0 12 0c0-3.2-2.6-7.1-6-11.3z"/></svg></span>',
+ '<span class="park-fac park-fac-water" title="水飲み場あり" aria-label="水飲み場あり">🚰</span>',
);
}
const clock = (p["時計"] || "").trim();
if (clock === "○" || clock === "あり" || clock === "有" || clock === "1") {
icons.push(
- '<span class="park-fac park-fac-clock" title="時計あり" aria-label="時計あり">⏰</span>',
+ '<span class="park-fac park-fac-clock" title="時計あり" aria-label="時計あり"><svg viewBox="0 0 24 24" width="12" height="12" aria-hidden="true"><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2"/><path d="M12 7v5l3 2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></span>',
);
}
const disaster = p["防災用具"] || "";
@@ -1207,6 +1284,26 @@
playMarkersByName.set(name, list);
});
+ // 公園名のラベル(高ズーム時のみ)
+ const escapeHtml = (value: string) =>
+ value.replace(/[&<>"']/g, (ch) =>
+ ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[ch] as string,
+ );
+ const parkNameLayer = L.layerGroup();
+ layersByName.forEach((lyr, name) => {
+ const center = centerOfLayer(lyr);
+ L.marker([center.lat, center.lng], {
+ icon: L.divIcon({
+ className: "park-name-marker",
+ html: `<span class="park-name-label">${escapeHtml(name)}</span>`,
+ iconSize: null,
+ iconAnchor: [0, 0],
+ }),
+ interactive: false,
+ keyboard: false,
+ }).addTo(parkNameLayer);
+ });
+
// フィルタ中は、該当しない公園の設備・遊具アイコンを隠す
const applyIconFilter = (names: Set<string> | null) => {
const toggle = (markersByName: Map<string, any[]>, layer: any) => {
@@ -1234,6 +1331,17 @@
} else if (map.hasLayer(playLayer)) {
map.removeLayer(playLayer);
}
+ if (map.getZoom() >= 17) {
+ if (!map.hasLayer(parkNameLayer)) parkNameLayer.addTo(map);
+ } else if (map.hasLayer(parkNameLayer)) {
+ map.removeLayer(parkNameLayer);
+ }
+ // 公共施設の名前は少し引いたズームから表示する
+ const showFacilityLabels = map.getZoom() >= 14;
+ facilityMarkers.forEach(({ layer }) => {
+ if (showFacilityLabels) layer.openTooltip();
+ else layer.closeTooltip();
+ });
};
map.on("zoomend", updateFacilities);
updateFacilities();
@@ -1347,6 +1455,7 @@
const detail = (e as CustomEvent).detail || {};
const names = new Set<string>(detail.names || []);
const active = Boolean(detail.active);
+ if (filterStatus) filterStatus.hidden = !active;
const fromMap = Boolean(detail.fromMap);
if (!fromMap && selectedBar) selectedBar.hidden = true;