mirror of
https://github.com/tabler/tabler.git
synced 2026-10-14 06:07:07 +04:00
Add HTML map markers and a popup card for MapLibre maps
This commit is contained in:
1 parent
5b2cddc30b
commit
b6904b93bd
8 files changed
+377
-16
No files matched your search
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@tabler/core": minor
|
||||
"@tabler/preview": minor
|
||||
"@tabler/docs": patch
|
||||
---
|
||||
|
||||
Added `.map-marker` with dot, pin and color variants and `.map-popup-card` for MapLibre maps to `tabler-vendors.css`.
|
||||
+6
-6
@@ -196,19 +196,19 @@
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/tabler-vendors.css",
|
||||
"maxSize": "4.5 kB"
|
||||
"maxSize": "5 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/tabler-vendors.min.css",
|
||||
"maxSize": "4 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/tabler-vendors.rtl.css",
|
||||
"maxSize": "4.5 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/tabler-vendors.rtl.css",
|
||||
"maxSize": "5 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/css/tabler-vendors.rtl.min.css",
|
||||
"maxSize": "4 kB"
|
||||
"maxSize": "4.5 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/js/tabler.js",
|
||||
|
||||
@@ -1279,6 +1279,19 @@ $ribbon-bookmark-border-size: 1rem !default;
|
||||
$ribbon-bookmark-notch-width: 0.5rem !default;
|
||||
// scss-docs-end ribbon-variables
|
||||
|
||||
// scss-docs-start map-marker-variables
|
||||
$map-marker-size: 1.75rem !default;
|
||||
$map-marker-dot-size: 0.875rem !default;
|
||||
$map-marker-bg: var(--primary) !default;
|
||||
$map-marker-color: var(--primary-fg) !default;
|
||||
$map-marker-border-width: 2px !default;
|
||||
$map-marker-border-color: var(--bg-surface) !default;
|
||||
$map-marker-font-size: $h5-font-size !default;
|
||||
$map-marker-icon-size: 1rem !default;
|
||||
$map-marker-shadow: drop-shadow(0 1px 2px rgba($black, 0.3)) !default;
|
||||
$map-popup-card-width: 16rem !default;
|
||||
// scss-docs-end map-marker-variables
|
||||
|
||||
// Accordion
|
||||
// scss-docs-start accordion-variables
|
||||
$accordion-padding-y: 1rem !default;
|
||||
|
||||
@@ -37,17 +37,21 @@ exports[`css custom-property prefixing > leaves vendor-owned names alone and pre
|
||||
"--plyr-color-main",
|
||||
"--tblr-active-bg",
|
||||
"--tblr-azure",
|
||||
"--tblr-azure-fg",
|
||||
"--tblr-backdrop-bg",
|
||||
"--tblr-bg-forms",
|
||||
"--tblr-bg-surface",
|
||||
"--tblr-bg-surface-inverted",
|
||||
"--tblr-bg-surface-secondary",
|
||||
"--tblr-black",
|
||||
"--tblr-blue",
|
||||
"--tblr-blue-fg",
|
||||
"--tblr-body-bg",
|
||||
"--tblr-body-color",
|
||||
"--tblr-border-color",
|
||||
"--tblr-border-color-translucent",
|
||||
"--tblr-border-radius",
|
||||
"--tblr-border-radius-pill",
|
||||
"--tblr-border-style",
|
||||
"--tblr-border-width",
|
||||
"--tblr-card-bg",
|
||||
@@ -61,7 +65,12 @@ exports[`css custom-property prefixing > leaves vendor-owned names alone and pre
|
||||
"--tblr-control-padding-x",
|
||||
"--tblr-control-padding-y",
|
||||
"--tblr-control-placeholder-color",
|
||||
"--tblr-cyan",
|
||||
"--tblr-cyan-fg",
|
||||
"--tblr-danger",
|
||||
"--tblr-danger-fg",
|
||||
"--tblr-dark",
|
||||
"--tblr-dark-fg",
|
||||
"--tblr-focus-ring",
|
||||
"--tblr-focus-ring-color",
|
||||
"--tblr-focus-ring-offset",
|
||||
@@ -79,26 +88,56 @@ exports[`css custom-property prefixing > leaves vendor-owned names alone and pre
|
||||
"--tblr-form-valid-border-color",
|
||||
"--tblr-gray-500",
|
||||
"--tblr-green",
|
||||
"--tblr-green-fg",
|
||||
"--tblr-icon-size",
|
||||
"--tblr-indigo",
|
||||
"--tblr-indigo-fg",
|
||||
"--tblr-info",
|
||||
"--tblr-info-fg",
|
||||
"--tblr-light",
|
||||
"--tblr-light-fg",
|
||||
"--tblr-lime",
|
||||
"--tblr-lime-fg",
|
||||
"--tblr-map-marker-bg",
|
||||
"--tblr-map-marker-border-color",
|
||||
"--tblr-map-marker-border-width",
|
||||
"--tblr-map-marker-color",
|
||||
"--tblr-map-marker-font-size",
|
||||
"--tblr-map-marker-icon-size",
|
||||
"--tblr-map-marker-size",
|
||||
"--tblr-muted",
|
||||
"--tblr-muted-fg",
|
||||
"--tblr-orange",
|
||||
"--tblr-orange-fg",
|
||||
"--tblr-pink",
|
||||
"--tblr-pink-fg",
|
||||
"--tblr-primary",
|
||||
"--tblr-primary-200",
|
||||
"--tblr-primary-fg",
|
||||
"--tblr-primary-lt",
|
||||
"--tblr-purple",
|
||||
"--tblr-purple-fg",
|
||||
"--tblr-red",
|
||||
"--tblr-red-fg",
|
||||
"--tblr-secondary",
|
||||
"--tblr-secondary-bg",
|
||||
"--tblr-secondary-fg",
|
||||
"--tblr-shadow-card",
|
||||
"--tblr-shadow-color",
|
||||
"--tblr-shadow-dropdown",
|
||||
"--tblr-shadow-input",
|
||||
"--tblr-signal-color",
|
||||
"--tblr-success",
|
||||
"--tblr-success-fg",
|
||||
"--tblr-teal",
|
||||
"--tblr-teal-fg",
|
||||
"--tblr-tertiary",
|
||||
"--tblr-text-inverted",
|
||||
"--tblr-warning",
|
||||
"--tblr-warning-fg",
|
||||
"--tblr-white",
|
||||
"--tblr-yellow",
|
||||
"--tblr-yellow-fg",
|
||||
"--ts-pr-caret",
|
||||
"--ts-pr-clear-button",
|
||||
]
|
||||
|
||||
Vendored
+85
@@ -122,6 +122,91 @@
|
||||
border-left-color: var(--bg-surface);
|
||||
}
|
||||
|
||||
// Popup that holds a card-like block: an image on top, padded content below
|
||||
.map-popup-card {
|
||||
.maplibregl-popup-content {
|
||||
width: #{$map-popup-card-width};
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.maplibregl-popup-close-button {
|
||||
color: var(--body-color);
|
||||
background-color: var(--bg-surface);
|
||||
}
|
||||
}
|
||||
|
||||
// Marker drawn in HTML. MapLibre moves the marker with `transform`, so the
|
||||
// shape is drawn by the pseudo-element and the root keeps no transform.
|
||||
.map-marker {
|
||||
// scss-docs-start map-marker-css-vars
|
||||
--map-marker-size: #{$map-marker-size};
|
||||
--map-marker-bg: #{$map-marker-bg};
|
||||
--map-marker-color: #{$map-marker-color};
|
||||
--map-marker-border-width: #{$map-marker-border-width};
|
||||
--map-marker-border-color: #{$map-marker-border-color};
|
||||
--map-marker-font-size: #{$map-marker-font-size};
|
||||
--map-marker-icon-size: #{$map-marker-icon-size};
|
||||
// scss-docs-end map-marker-css-vars
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: var(--map-marker-size);
|
||||
min-height: var(--map-marker-size);
|
||||
padding: var(--map-marker-border-width);
|
||||
font-size: var(--map-marker-font-size);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1;
|
||||
color: var(--map-marker-color);
|
||||
cursor: pointer;
|
||||
filter: #{$map-marker-shadow};
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
content: '';
|
||||
background: var(--map-marker-bg);
|
||||
border: var(--map-marker-border-width) solid var(--map-marker-border-color);
|
||||
@include border-radius(var(--border-radius-pill));
|
||||
}
|
||||
|
||||
.icon {
|
||||
--icon-size: var(--map-marker-icon-size);
|
||||
}
|
||||
}
|
||||
|
||||
.map-marker-dot {
|
||||
--map-marker-size: #{$map-marker-dot-size};
|
||||
}
|
||||
|
||||
// Teardrop: a square with one sharp corner, turned to point down. Its tip
|
||||
// reaches 20.7% below the circle, so the root is that much taller.
|
||||
.map-marker-pin {
|
||||
box-sizing: content-box;
|
||||
width: var(--map-marker-size);
|
||||
min-width: 0;
|
||||
height: var(--map-marker-size);
|
||||
min-height: 0;
|
||||
padding: 0 0 calc(var(--map-marker-size) * 0.207);
|
||||
|
||||
&::before {
|
||||
bottom: auto;
|
||||
height: var(--map-marker-size);
|
||||
@include border-radius(50% 50% 50% 0);
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
|
||||
@each $name, $color in $theme-colors {
|
||||
.map-marker-#{$name} {
|
||||
--map-marker-bg: var(--#{$name});
|
||||
--map-marker-color: var(--#{$name}-fg);
|
||||
}
|
||||
}
|
||||
|
||||
// The control icons are black SVG images
|
||||
@if $enable-dark-mode {
|
||||
@include color-mode(dark) {
|
||||
|
||||
@@ -4,17 +4,31 @@ summary: A map shows places and markers on an interactive world map. Tabler prov
|
||||
description: Show interactive maps with markers using MapLibre GL JS and free map tiles. No account or access token is needed.
|
||||
related: [/ui/plugins/vector-map]
|
||||
docs-libs: [maplibre-gl]
|
||||
source: core/scss/_utilities.scss
|
||||
source: core/scss/vendor/_maplibre.scss
|
||||
css-vars: map-marker
|
||||
classnames:
|
||||
component:
|
||||
- class: ratio
|
||||
desc: Wrapper that keeps the embed at a fixed aspect ratio
|
||||
desc: Wrapper that keeps the map at a fixed aspect ratio
|
||||
- class: map-marker
|
||||
desc: Marker drawn in HTML, a circle that can hold an icon, a count or an avatar
|
||||
modifier:
|
||||
- class: map-marker-dot
|
||||
desc: Small dot with no content
|
||||
- class: map-marker-pin
|
||||
desc: Teardrop pin that points down, used with the bottom anchor
|
||||
- class: map-popup-card
|
||||
desc: Popup with no padding and a fixed width, for an image above the text
|
||||
color:
|
||||
- class: map-marker-{color}
|
||||
desc: Any theme color, for example map-marker-red or map-marker-green
|
||||
size:
|
||||
- class: ratio-{ratio}
|
||||
desc: The aspect to hold, for example ratio-16x9 or ratio-21x9
|
||||
---
|
||||
import Example from '@components/Example.astro';
|
||||
import Map from '@shared/components/demo/Map.astro';
|
||||
import Icon from '@ui/Icon.astro';
|
||||
import { Code } from 'astro:components';
|
||||
import { site } from '@shared/lib/site.ts';
|
||||
|
||||
@@ -122,6 +136,69 @@ cities.forEach(function (coords) {
|
||||
|
||||
The `color` option is written to the `fill` attribute of the marker icon. A CSS variable works there, so `var(--tblr-primary)` keeps the markers in your theme color, including after a theme change.
|
||||
|
||||
### Custom markers
|
||||
|
||||
To draw a marker in HTML, create an element with the `.map-marker` class and pass it as `element`. The marker is a circle in the primary color. Put an icon, a short count or an avatar inside it.
|
||||
|
||||
```js
|
||||
const element = document.createElement('span')
|
||||
element.className = 'map-marker map-marker-green'
|
||||
element.textContent = '12'
|
||||
|
||||
new maplibregl.Marker({ element: element }).setLngLat([13.35, 52.512]).addTo(map)
|
||||
```
|
||||
|
||||
Add `.map-marker-dot` for a small dot, or `.map-marker-pin` for a pin that points down. A pin needs `anchor: 'bottom'`, so its tip sits on the place. Change the color with `.map-marker-{color}`.
|
||||
|
||||
```js
|
||||
const pin = document.createElement('span')
|
||||
pin.className = 'map-marker map-marker-pin map-marker-red'
|
||||
|
||||
new maplibregl.Marker({ element: pin, anchor: 'bottom' }).setLngLat([13.47, 52.518]).addTo(map)
|
||||
```
|
||||
|
||||
<Example>
|
||||
<Map mapId="marker-styles" title="Marker styles" center={[52.522, 13.405]} zoom={11}>
|
||||
<div data-lng="13.345" data-lat="52.54"><span class="map-marker map-marker-dot"></span></div>
|
||||
<div data-lng="13.39" data-lat="52.545"><span class="map-marker map-marker-dot map-marker-red"></span></div>
|
||||
<div data-lng="13.445" data-lat="52.538"><span class="map-marker"><Icon name="building-store" /></span></div>
|
||||
<div data-lng="13.35" data-lat="52.512"><span class="map-marker map-marker-green">12</span></div>
|
||||
<div data-lng="13.405" data-lat="52.522" data-anchor="bottom"><span class="map-marker map-marker-pin"><Icon name="home" /></span></div>
|
||||
<div data-lng="13.47" data-lat="52.518" data-anchor="bottom"><span class="map-marker map-marker-pin map-marker-red"><Icon name="star" /></span></div>
|
||||
<div data-lng="13.43" data-lat="52.508"><span class="map-marker"><span class="avatar avatar-sm" style="background-image: url(/static/avatars/000m.jpg)"></span></span></div>
|
||||
</Map>
|
||||
</Example>
|
||||
|
||||
You can also write the markers in the page and move them to the map. The preview pages do this: each marker sits in a hidden element with `data-lng` and `data-lat`, and a short loop passes it to MapLibre.
|
||||
|
||||
```html
|
||||
<div id="map-markers" hidden>
|
||||
<div data-lng="13.405" data-lat="52.522">
|
||||
<span class="map-marker map-marker-pin">...</span>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```js
|
||||
document.querySelectorAll('#map-markers > [data-lng]').forEach(function (item) {
|
||||
new maplibregl.Marker({ element: item.firstElementChild, anchor: 'bottom' })
|
||||
.setLngLat([Number(item.dataset.lng), Number(item.dataset.lat)])
|
||||
.addTo(map)
|
||||
})
|
||||
```
|
||||
|
||||
### Draggable markers
|
||||
|
||||
Pass `draggable: true` to let people move a marker, for example to pick an address. Read the new position in the `dragend` event.
|
||||
|
||||
```js
|
||||
const marker = new maplibregl.Marker({ element: pin, anchor: 'bottom', draggable: true }).setLngLat([13.405, 52.52]).addTo(map)
|
||||
|
||||
marker.on('dragend', function () {
|
||||
console.log(marker.getLngLat())
|
||||
})
|
||||
```
|
||||
|
||||
### Popups
|
||||
|
||||
Attach a popup to a marker with `setPopup`. It opens when the marker is clicked. Click a marker on the map above to see one.
|
||||
@@ -135,6 +212,44 @@ new maplibregl.Marker({ color: 'var(--tblr-primary)' })
|
||||
|
||||
Use `setHTML` instead of `setText` for richer content. Only pass HTML you trust, as it is not escaped.
|
||||
|
||||
### Popup card
|
||||
|
||||
For a popup with an image, add the `map-popup-card` class with the `className` option. It removes the padding and sets a fixed width, so the image reaches the edges. Keep the content in a `<template>` and pass it with `setDOMContent`.
|
||||
|
||||
```html
|
||||
<template id="popup-museum">
|
||||
<div class="img-responsive img-responsive-21x9" style="background-image: url(...)"></div>
|
||||
<div class="p-3">
|
||||
<div class="fw-medium">Museum Island</div>
|
||||
<div class="text-secondary mb-3">Five museums on the Spree, open daily.</div>
|
||||
<a href="#" class="btn btn-primary btn-sm">View details</a>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
```js
|
||||
const popup = new maplibregl.Popup({ offset: 40, className: 'map-popup-card', maxWidth: 'none' })
|
||||
.setDOMContent(document.getElementById('popup-museum').content)
|
||||
|
||||
new maplibregl.Marker({ element: pin, anchor: 'bottom' }).setLngLat([13.4019, 52.5169]).setPopup(popup).addTo(map)
|
||||
```
|
||||
|
||||
<Example>
|
||||
<Map mapId="popup-card" title="Popup card" center={[52.546, 13.4]} zoom={11} ratio="4x3">
|
||||
<div data-lng="13.4019" data-lat="52.5169" data-anchor="bottom" data-open>
|
||||
<span class="map-marker map-marker-pin"><Icon name="map-pin" /></span>
|
||||
<template class="map-popup-card">
|
||||
<div class="img-responsive img-responsive-21x9" style="background-image: url(/static/photos/a-visit-to-the-bookstore.jpg)"></div>
|
||||
<div class="p-3">
|
||||
<div class="fw-medium">Museum Island</div>
|
||||
<div class="text-secondary mb-3">Five museums on the Spree, open daily.</div>
|
||||
<a href="#" class="btn btn-primary btn-sm">View details</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</Map>
|
||||
</Example>
|
||||
|
||||
### Controls
|
||||
|
||||
Add the zoom and compass buttons with `NavigationControl`. They go to the top right corner by default.
|
||||
|
||||
@@ -5,13 +5,23 @@ import Card from '@ui/Card.astro'
|
||||
import CardBody from '@ui/CardBody.astro'
|
||||
import maps from '@data/maps.json'
|
||||
import CardTitle from '@ui/CardTitle.astro'
|
||||
import CardSubtitle from '@ui/CardSubtitle.astro'
|
||||
import DocsLink from '@ui/DocsLink.astro'
|
||||
import Avatar from '@ui/Avatar.astro'
|
||||
import Icon from '@ui/Icon.astro'
|
||||
import { staticPath } from '@shared/lib/assets'
|
||||
|
||||
type MapData = { title?: string; card?: boolean }
|
||||
const mapEntries = Object.entries(maps as Record<string, MapData>)
|
||||
|
||||
const places = [
|
||||
{ name: 'Museum Island', text: 'Five museums on the Spree, open daily.', photo: 'a-visit-to-the-bookstore.jpg', lat: 52.5169, lng: 13.4019, open: true },
|
||||
{ name: 'Bellevue Palace', text: 'Residence by the Tiergarten park.', photo: 'book-on-the-grass.jpg', lat: 52.5176, lng: 13.3531, open: false },
|
||||
{ name: 'Weissensee', text: 'A lake with a beach in the north-east.', photo: 'brainstorming-session-with-creative-designers.jpg', lat: 52.5546, lng: 13.4633, open: false },
|
||||
]
|
||||
---
|
||||
|
||||
<DefaultLayout title="Maps" pageMenu="plugins.maps" pageLibs={['maplibre-gl']} description="Interactive MapLibre maps — plain, styled, or full-card with markers.">
|
||||
<DefaultLayout title="Maps" pageMenu="plugins.maps" pageLibs={['maplibre-gl']} description="Interactive MapLibre maps — plain, styled, with custom markers and popups.">
|
||||
<DocsLink slot="page-header-actions" path="/ui/components/map" />
|
||||
<div class="row row-cards">
|
||||
{
|
||||
@@ -34,5 +44,65 @@ const mapEntries = Object.entries(maps as Record<string, MapData>)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
<div class="col-lg-6">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Marker styles</CardTitle>
|
||||
<CardSubtitle>Draw markers in HTML with <code>.map-marker</code>: a dot, a circle with an icon or a count, a pin, or an avatar.</CardSubtitle>
|
||||
<Map mapId="marker-styles" title="Marker styles" center={[52.522, 13.405]} zoom={11} ratio="4x3">
|
||||
<div data-lng="13.345" data-lat="52.54"><span class="map-marker map-marker-dot"></span></div>
|
||||
<div data-lng="13.39" data-lat="52.545"><span class="map-marker map-marker-dot map-marker-red"></span></div>
|
||||
<div data-lng="13.445" data-lat="52.538"><span class="map-marker"><Icon name="building-store" /></span></div>
|
||||
<div data-lng="13.35" data-lat="52.512"><span class="map-marker map-marker-green">12</span></div>
|
||||
<div data-lng="13.405" data-lat="52.52" data-anchor="bottom"><span class="map-marker map-marker-pin"><Icon name="home" /></span></div>
|
||||
<div data-lng="13.47" data-lat="52.518" data-anchor="bottom"><span class="map-marker map-marker-pin map-marker-red"><Icon name="star" /></span></div>
|
||||
<div data-lng="13.43" data-lat="52.503"><span class="map-marker"><Avatar size="sm" src={staticPath('avatars/000m.jpg')} /></span></div>
|
||||
</Map>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Popup card</CardTitle>
|
||||
<CardSubtitle>Put the popup content in a <code><template></code> next to the marker. <code>.map-popup-card</code> makes room for an image.</CardSubtitle>
|
||||
<Map mapId="popup-card" title="Popup card" center={[52.546, 13.4]} zoom={11} ratio="4x3">
|
||||
{
|
||||
places.map((place) => (
|
||||
<div data-lng={place.lng} data-lat={place.lat} data-anchor="bottom" data-open={place.open ? '' : undefined}>
|
||||
<span class="map-marker map-marker-pin">
|
||||
<Icon name="map-pin" />
|
||||
</span>
|
||||
<template class="map-popup-card">
|
||||
<div class="img-responsive img-responsive-21x9" style={`background-image: url(${staticPath(`photos/${place.photo}`)})`} />
|
||||
<div class="p-3">
|
||||
<div class="fw-medium">{place.name}</div>
|
||||
<div class="text-secondary mb-3">{place.text}</div>
|
||||
<a href="#" class="btn btn-primary btn-sm">
|
||||
View details
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</Map>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-12">
|
||||
<Card>
|
||||
<CardBody>
|
||||
<CardTitle>Draggable marker</CardTitle>
|
||||
<CardSubtitle>Add <code>draggable: true</code> to let people move a marker, for example to pick an address.</CardSubtitle>
|
||||
<Map mapId="draggable" title="Draggable marker" center={[52.52, 13.405]} zoom={12} ratio="21x9">
|
||||
<div data-lng="13.405" data-lat="52.52" data-anchor="bottom" data-draggable><span class="map-marker map-marker-pin"><Icon name="map-pin" /></span></div>
|
||||
</Map>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
@@ -1,34 +1,42 @@
|
||||
---
|
||||
// MapLibre GL map on OpenFreeMap tiles: no account and no access token.
|
||||
// The map follows the page theme: `style` in light mode, `darkStyle` in dark.
|
||||
// Renders nothing unless maps[mapId] exists.
|
||||
// Markers written in HTML go in the default slot: each child has `data-lng`
|
||||
// and `data-lat`, holds the marker element, and optionally a `<template>` with
|
||||
// the popup content. Renders nothing unless maps[mapId] exists or `center` is set.
|
||||
import maps from '@data/maps.json';
|
||||
import CaptureScript from '../CaptureScript.astro';
|
||||
|
||||
interface Props {
|
||||
/** Key into the maps data */
|
||||
/** Key into the maps data; also the suffix of the container id */
|
||||
mapId: string;
|
||||
ratio?: string;
|
||||
/** for a map with no entry in the maps data: [latitude, longitude] */
|
||||
center?: [number, number];
|
||||
zoom?: number;
|
||||
title?: string;
|
||||
style?: string;
|
||||
}
|
||||
|
||||
const { mapId, ratio } = Astro.props;
|
||||
const { mapId, ratio, center: centerProp, zoom: zoomProp, title, style: styleProp } = Astro.props;
|
||||
|
||||
type MapData = {
|
||||
title?: string;
|
||||
title?: string | undefined;
|
||||
ratio?: string;
|
||||
card?: boolean;
|
||||
/** OpenFreeMap style name: liberty (default), bright, positron, dark or fiord */
|
||||
style?: string;
|
||||
style?: string | undefined;
|
||||
/** style used in dark mode, `dark` by default */
|
||||
darkStyle?: string;
|
||||
/** adds the zoom and compass buttons */
|
||||
controls?: boolean;
|
||||
zoom?: number;
|
||||
zoom?: number | undefined;
|
||||
center?: [number, number];
|
||||
markers?: { name?: string; center: [number, number] }[];
|
||||
};
|
||||
|
||||
const data = (maps as unknown as Record<string, MapData>)[mapId];
|
||||
const data: MapData | undefined = (maps as unknown as Record<string, MapData>)[mapId] ?? (centerProp ? { title, center: centerProp, zoom: zoomProp, style: styleProp } : undefined);
|
||||
const hasHtmlMarkers = Astro.slots.has('default');
|
||||
|
||||
const mapContainerId = `map-${mapId}`;
|
||||
const styleUrl = (name: string) => `https://tiles.openfreemap.org/styles/${name}`;
|
||||
@@ -51,6 +59,13 @@ const ratioClass = data?.ratio ?? ratio ?? '16x9';
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
data && hasHtmlMarkers && (
|
||||
<div id={`${mapContainerId}-markers`} hidden>
|
||||
<slot />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
data && (
|
||||
<CaptureScript>
|
||||
@@ -79,6 +94,23 @@ const ratioClass = data?.ratio ?? ratio ?? '16x9';
|
||||
.addTo(map);
|
||||
});
|
||||
|
||||
// Markers written in HTML: the first child is the marker, a <template> is its popup
|
||||
document.querySelectorAll(`#${mapContainerId}-markers > [data-lng]`).forEach((item) => {
|
||||
const anchor = item.dataset.anchor ?? 'center';
|
||||
const marker = new maplibregl.Marker({ element: item.firstElementChild, anchor, draggable: 'draggable' in item.dataset });
|
||||
const popup = item.querySelector('template');
|
||||
|
||||
if (popup) {
|
||||
marker.setPopup(new maplibregl.Popup({ offset: anchor === 'bottom' ? 40 : 20, className: popup.className, maxWidth: 'none', focusAfterOpen: !('open' in item.dataset) }).setDOMContent(popup.content));
|
||||
}
|
||||
|
||||
marker.setLngLat([Number(item.dataset.lng), Number(item.dataset.lat)]).addTo(map);
|
||||
|
||||
if (popup && 'open' in item.dataset) {
|
||||
marker.togglePopup();
|
||||
}
|
||||
});
|
||||
|
||||
// Change the map style when the page theme changes
|
||||
new MutationObserver(() => map.setStyle(themeStyle())).observe(document.documentElement, { attributeFilter: ['data-bs-theme'] });
|
||||
|
||||
|
||||
Reference in new issue
Block a user