diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-11-06 20:56:59 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-11-06 20:56:59 +0900 |
| commit | 9e8fb9a3952bfe7e7c6ca54a656270f99545e6c6 (patch) | |
| tree | 062fd6fb32c4bd01d86aa6f5909624415ca1ac4d /src/components | |
| parent | 3df9fffd0e927f92dffe8b986d1ab8c61d4b6484 (diff) | |
parks: 全画面パネルとアイコン・名前表示を改善
全画面パネルに「適用中」表示と下部の注記・閉じるボタンを追加。時計をSVG、水栓を蛇口に変更。高ズームで公園名、公共施設のピン上に名前を表示。市外公園クリックでもキーワードを変えない。
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/ParkMap.astro | 159 | ||||
| -rw-r--r-- | src/components/ParkMapHelp.astro | 4 |
2 files changed, 136 insertions, 27 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) => + ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[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; diff --git a/src/components/ParkMapHelp.astro b/src/components/ParkMapHelp.astro index 5703b8c..e45b87f 100644 --- a/src/components/ParkMapHelp.astro +++ b/src/components/ParkMapHelp.astro @@ -14,8 +14,8 @@ <span><i class="swatch swatch-facility-nearby"></i>市外の公共施設</span> <span><i class="swatch swatch-boundary"></i>小平市の境界</span> <span><i class="swatch-fac swatch-fac-toilet">WC</i>トイレ</span> - <span><i class="swatch-fac swatch-fac-water"><svg viewBox="0 0 24 24" width="10" height="10" 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></i>水飲み場(拡大時)</span> - <span><i class="swatch-fac swatch-fac-clock">⏰</i>時計(拡大時)</span> + <span><i class="swatch-fac swatch-fac-water">🚰</i>水飲み場(拡大時)</span> + <span><i class="swatch-fac swatch-fac-clock"><svg viewBox="0 0 24 24" width="10" height="10" 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></i>時計(拡大時)</span> <span><i class="swatch-fac swatch-fac-hydrant">消</i>消火栓(拡大時)</span> <span><i class="swatch-fac swatch-fac-tank">槽</i>防火水槽(拡大時)</span> <span><i class="swatch-fac swatch-fac-unconfirmed">?</i>情報未確認(拡大時)</span> |
