Skip to content

Commit

Permalink
Merge pull request #22 from andrewgryan/feature/pane
Browse files Browse the repository at this point in the history
Add support for l-pane custom element
  • Loading branch information
andrewgryan authored Jul 14, 2024
2 parents bbf2c3b + 67fb868 commit 11fc14b
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 27 deletions.
81 changes: 81 additions & 0 deletions docs/content/articles/panes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
+++
title = "Working with map panes"
+++

## Demo

This tutorial illustrates how to make effective use of Leaflet panes.

<style>
.leaflet-coastline-pane {
z-index: 450;
pointer-events: none;
}
</style>

<l-map zoom="3" center="[55, 0]">
<l-tile-layer url-template="https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png"></l-tile-layer>
<l-pane name="coastline">
<l-tile-layer url-template="https://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png"></l-tile-layer>
</l-pane>
<l-geojson id="countries"></l-geojson>
</l-map>

<script type="text/javascript" src="/leaflet-html/panes/eu-countries.js"></script>
<script type="text/javascript" src="/panes/eu-countries.js"></script>
<script>
document.getElementById("countries").setAttribute("geojson", JSON.stringify(euCountries))
</script>

Following the tutorial on the Leaflet JS website.

### Mark-up

The mark-up for a custom pane is relatively straight-forward.
Add a `l-pane` tag with a `name` attribute to instruct `leaflet-html` to add a new pane.

```html,hl_lines=6 10,linenos
<!-- Example app using a Pane -->
<l-map zoom="3" center="[55, 0]">
<l-tile-layer
url-template="https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}.png"
></l-tile-layer>
<l-pane name="coastline">
<l-tile-layer
url-template="https://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}.png"
></l-tile-layer>
</l-pane>
<l-geojson id="countries"></l-geojson>
</l-map>
```

Lines 6 and 10 show the modifications required to place a layer into a pane.

### Control the z-index and pointer events

```css
/* Style the element generated by Leaflet JS */
.leaflet-coastline-pane {
z-index: 450;
pointer-events: none;
}
```

### Sample data

In a real application, the HTML will be server rendered with geoJSON.
But for this short demo the following snippet of JS has been used.

```js
/** Sample data */
document
.getElementById("countries")
.setAttribute("geojson", JSON.stringify(euCountries));
```

## Conclusion

Leaflet HTML supports custom panes for those times when controlling
the z-index of a collection of layers is important.


79 changes: 79 additions & 0 deletions docs/static/panes/eu-countries.js

Large diffs are not rendered by default.

40 changes: 18 additions & 22 deletions docs/static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,27 @@ h3 {
}

h1 {
--color-1: #550527;
--color-2: #688e26;
--color-3: #faa613;
--color-4: #f44708;
--color-5: #a10702;
--angle: 160deg;
--color-1: hsl(var(--angle) 70% 50%);
--color-2: hsl(from var(--color-1) calc(180deg + var(--angle)) s l);
--color-3: hsl(from var(--color-1) calc(180deg - var(--angle)) s l);
--color-4: hsl(from var(--color-1) calc(360deg - var(--angle)) s l);
color: transparent;
text-wrap: balance;
background: linear-gradient(
135deg,
var(--color-1) calc(19% - 1px),
transparent 19%,
transparent calc(20% - 1px),
var(--color-2) 20%,
var(--color-2) calc(39% - 1px),
transparent 39%,
transparent calc(40% - 1px),
var(--color-3) 40%,
var(--color-3) calc(59% - 1px),
transparent 59%,
transparent calc(60% - 1px),
var(--color-4) 60%,
var(--color-4) calc(79% - 1px),
transparent 79%,
transparent calc(80% - 1px),
var(--color-5) 80%
30deg,
var(--color-1) 12.5%,
var(--color-2) 12.5%,
var(--color-2) 25%,
var(--color-3) 25%,
var(--color-3) 37.5%,
var(--color-4) 37.5%,
var(--color-4) 50%,
var(--color-1) 50%,
var(--color-1) 62.5%,
var(--color-2) 62.5%,
var(--color-2) 75%,
var(--color-3) 75%
);
background-clip: text;
-webkit-background-clip: text;
Expand Down
1 change: 1 addition & 0 deletions src/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export const layerRemoved = "l:layer:removed";
export const latLngBoundsConnected = "l:latlngbounds:connected";
export const latLngBoundsChanged = "l:latlngbounds:changed";
export const tooltipConnected = "l:tooltip:connected";
export const paneConnected = "l:pane:connected";
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ import LVideoOverlay from "./l-video-overlay.js";
import LGeoJSON from "./l-geojson.js";
import LIcon from "./l-icon.js";
import LTooltip from "./l-tooltip.js";
import LPane from "./l-pane.js";
import generator from "./generator.js";
import { circle, polyline, polygon, rectangle } from "leaflet";

const init = (() => {
// Custom elements (order of definition is important)
customElements.define("l-map", LMap);
customElements.define("l-pane", LPane);
customElements.define("l-control-layers", LControlLayers);
customElements.define("l-base-layers", LBaseLayers);
customElements.define("l-overlay-layers", LOverlayLayers);
Expand Down
15 changes: 12 additions & 3 deletions src/l-geojson.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,29 @@ import { layerConnected } from "./events.js";
class LGeoJSON extends HTMLElement {
constructor() {
super();
this.layer = null;
}

connectedCallback() {
const value = this.getAttribute("geojson");
const options = {};

// Pane
const pane = this.closest("l-pane");
if (pane !== null) {
options["pane"] = pane.getAttribute("name");
}

if (value !== null) {
const layer = geoJSON(JSON.parse(value));
this.layer = geoJSON(JSON.parse(value), options);
this.dispatchEvent(
new CustomEvent(layerConnected, {
bubbles: true,
cancelable: true,
detail: {
layer,
layer: this.layer,
},
}),
})
);
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/l-image-overlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ class LImageOverlay extends LLayer {
opacity: parseFloat(this.getAttribute("opacity") || "1.0"),
alt: this.getAttribute("alt") || "",
};

// Pane
const pane = this.closest("l-pane");
if (pane !== null) {
options["pane"] = pane.getAttribute("name");
}

this.layer = imageOverlay(url, JSON.parse(bounds), options);
this.dispatchEvent(
new CustomEvent(layerConnected, {
Expand Down
8 changes: 7 additions & 1 deletion src/l-map.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @ts-check
import * as L from "leaflet";
import { layerRemoved, layerConnected, latLngBoundsConnected, latLngBoundsChanged } from "./events.js";
import { layerRemoved, layerConnected, latLngBoundsConnected, latLngBoundsChanged, paneConnected } from "./events.js";
import LLayer from "./l-layer.js";
import { distribute, int, json, option, parse } from "./parse.js";

Expand All @@ -22,6 +22,12 @@ class LMap extends HTMLElement {
this.addEventListener(latLngBoundsConnected, boundsListener);
this.addEventListener(latLngBoundsChanged, boundsListener);

// Observe l-pane
this.addEventListener(paneConnected, (ev) => {
const { name } = ev.detail
this.map.createPane(name)
})

// Observe removed l-tile-layers
const observer = new MutationObserver(function (mutations) {
mutations.forEach((mutation) => {
Expand Down
7 changes: 7 additions & 0 deletions src/l-marker.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ class LMarker extends LLayer {
}),
this
);

// Pane
const pane = this.closest("l-pane");
if (pane !== null) {
options["pane"] = pane.getAttribute("name");
}

this.layer = L.marker(latLng, options);

if (this.hasAttribute("icon")) {
Expand Down
19 changes: 19 additions & 0 deletions src/l-pane.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { paneConnected } from "./events.js";

export default class CustomElement extends HTMLElement {
constructor() {
super();
}

connectedCallback() {
this.dispatchEvent(
new CustomEvent(paneConnected, {
bubbles: true,
cancelable: true,
detail: {
name: this.getAttribute("name"),
},
})
);
}
}
80 changes: 80 additions & 0 deletions src/l-pane.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// @vitest-environment happy-dom
import "./index.js";
import { expect, it } from "vitest";

const createMapElement = (zoom, center) => {
const root = document.createElement("l-map");
root.setAttribute("zoom", zoom.toString());
root.setAttribute("center", JSON.stringify(center));
return root;
};

it("should support pane", () => {
const layerName = "layer-name";
const root = createMapElement(0, [0, 0]);
const pane = document.createElement("l-pane");
pane.setAttribute("name", layerName);
root.appendChild(pane);
document.body.appendChild(root);

const actual = root.map.getPane(layerName);
expect(actual).toBeDefined();
});

// Add minimum attributes to each Custom Element
const fakeElement = (key) => {
const el = document.createElement(key);
switch (key) {
case "l-image-overlay":
el.setAttribute("url", "fake/url");
el.setAttribute("bounds", "[[0,0],[1,1]]");
break;
case "l-marker":
el.setAttribute("lat-lng", "[0, 0]");
break;
case "l-tile-layer":
el.setAttribute("url-template", "fake/{z}/{x}/{y}.png");
break;
case "l-geojson":
el.setAttribute("geojson", '{"type": "Feature", "properties": {}}');
break;
}
return el;
};

// Check each layer supports custom pane
it.each([["l-image-overlay"], ["l-marker"], ["l-tile-layer"], ["l-geojson"]])(
"should capture %s in pane",
(key) => {
const root = createMapElement(0, [0, 0]);
const pane = document.createElement("l-pane");
pane.setAttribute("name", "test-pane");
root.appendChild(pane);
const layerEl = fakeElement(key);
pane.appendChild(layerEl);
document.body.appendChild(root);

// Assert pane on map is the pane on the layer
const actual = root.map.getPane("test-pane");
const expected = layerEl.layer.getPane();
expect(actual).toEqual(expected);
}
);

// Check each layer supports custom pane
it.each([
["l-image-overlay", "overlayPane"],
["l-marker", "markerPane"],
["l-tile-layer", "tilePane"],
["l-geojson", "overlayPane"],
])("should match default pane %s", (key, pane) => {
const root = createMapElement(0, [0, 0]);
const layerEl = fakeElement(key);
root.appendChild(layerEl);
document.body.appendChild(root);

// Assert pane on map is the pane on the layer
const actual = root.map.getPane(pane);
const expected = layerEl.layer.getPane();
expect(actual).toEqual(expected);
});
9 changes: 8 additions & 1 deletion src/l-tile-layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ class LTileLayer extends LLayer {
templateOptions[attribute] = value
}
}

// Pane options
const paneOptions = {}
// Support <l-pane> parent element
if (this.parentElement.tagName.toLowerCase() === "l-pane") {
paneOptions["pane"] = this.parentElement.getAttribute("name")
}

// Options
const name = this.getAttribute("name");
Expand All @@ -30,7 +37,7 @@ class LTileLayer extends LLayer {
errorTileUrl: optional(htmlAttribute("error-tile-url"))
})
const options = parse(schema, this)
this.layer = tileLayer(urlTemplate, { ...templateOptions, ...options });
this.layer = tileLayer(urlTemplate, { ...templateOptions, ...paneOptions, ...options });
const event = new CustomEvent(layerConnected, {
detail: { name, layer: this.layer },
bubbles: true,
Expand Down

0 comments on commit 11fc14b

Please sign in to comment.