leaflet-html 0.1.7 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. package/.github/workflows/npm-publish.yml +27 -27
  2. package/.prettierignore +2 -0
  3. package/.prettierrc +1 -0
  4. package/README.md +63 -36
  5. package/dist/leaflet-html.cjs +1 -1
  6. package/dist/leaflet-html.cjs.map +1 -1
  7. package/dist/leaflet-html.esm.js +1 -1
  8. package/dist/leaflet-html.esm.js.map +1 -1
  9. package/dist/leaflet-html.js +1 -1
  10. package/dist/leaflet-html.js.map +1 -1
  11. package/dist/leaflet-html.umd.js +1 -1
  12. package/dist/leaflet-html.umd.js.map +1 -1
  13. package/docs/content/_index.md +66 -63
  14. package/docs/content/articles/_index.md +5 -0
  15. package/docs/content/articles/basic.md +105 -0
  16. package/docs/content/articles/icons.md +35 -0
  17. package/docs/content/articles/style.md +14 -0
  18. package/docs/public/icons/leaf-green.png +0 -0
  19. package/docs/public/icons/leaf-orange.png +0 -0
  20. package/docs/public/icons/leaf-red.png +0 -0
  21. package/docs/public/icons/leaf-shadow.png +0 -0
  22. package/docs/static/icons/leaf-green.png +0 -0
  23. package/docs/static/icons/leaf-orange.png +0 -0
  24. package/docs/static/icons/leaf-red.png +0 -0
  25. package/docs/static/icons/leaf-shadow.png +0 -0
  26. package/docs/templates/article-page.html +8 -0
  27. package/docs/templates/article.html +12 -0
  28. package/docs/templates/base.html +59 -0
  29. package/docs/templates/index.html +5 -52
  30. package/example/geojson/index.html +42 -0
  31. package/example/index.html +58 -50
  32. package/example/overlays/index.html +34 -35
  33. package/package.json +12 -5
  34. package/src/events.js +3 -0
  35. package/src/index.js +27 -185
  36. package/src/l-base-layers.js +15 -0
  37. package/src/l-control-layers.js +34 -0
  38. package/src/l-geojson.js +22 -0
  39. package/src/l-icon.js +124 -0
  40. package/src/l-image-overlay.js +43 -0
  41. package/src/l-lat-lng-bounds.js +20 -0
  42. package/src/l-layer-group.js +39 -0
  43. package/src/l-map.js +33 -0
  44. package/src/l-marker.js +94 -0
  45. package/src/l-overlay-layers.js +15 -0
  46. package/src/l-popup.js +21 -0
  47. package/src/l-tile-layer.js +22 -0
  48. package/src/l-video-overlay.js +31 -0
  49. package/vite.config.js +7 -0
@@ -0,0 +1,15 @@
1
+ import { mapAddTo } from "./events.js";
2
+
3
+ class LBaseLayers extends HTMLElement {
4
+ constructor() {
5
+ super();
6
+ }
7
+
8
+ connectedCallback() {
9
+ this.addEventListener(mapAddTo, (ev) => {
10
+ ev.detail["type"] = "base";
11
+ });
12
+ }
13
+ }
14
+
15
+ export default LBaseLayers;
@@ -0,0 +1,34 @@
1
+ import { mapAddTo } from "./events.js";
2
+
3
+ class LControlLayers extends HTMLElement {
4
+ constructor() {
5
+ super();
6
+ }
7
+
8
+ connectedCallback() {
9
+ const base = {};
10
+ const overlay = {};
11
+ const control = L.control.layers(base, overlay);
12
+
13
+ this.addEventListener(mapAddTo, (ev) => {
14
+ const { type, name, layer } = ev.detail;
15
+ if (type === "overlay") {
16
+ control.addOverlay(layer, name);
17
+ } else if (type === "base") {
18
+ control.addBaseLayer(layer, name);
19
+ }
20
+ ev.preventDefault();
21
+ });
22
+
23
+ const event = new CustomEvent(mapAddTo, {
24
+ cancelable: true,
25
+ bubbles: true,
26
+ detail: {
27
+ layer: control,
28
+ },
29
+ });
30
+ this.dispatchEvent(event);
31
+ }
32
+ }
33
+
34
+ export default LControlLayers;
@@ -0,0 +1,22 @@
1
+ import { mapAddTo } from "./events.js";
2
+
3
+ class LGeoJSON extends HTMLElement {
4
+ constructor() {
5
+ super();
6
+ }
7
+
8
+ connectedCallback() {
9
+ const layer = L.geoJSON(JSON.parse(this.getAttribute("geojson")));
10
+ this.dispatchEvent(
11
+ new CustomEvent(mapAddTo, {
12
+ bubbles: true,
13
+ cancelable: true,
14
+ detail: {
15
+ layer,
16
+ },
17
+ }),
18
+ );
19
+ }
20
+ }
21
+
22
+ export default LGeoJSON;
package/src/l-icon.js ADDED
@@ -0,0 +1,124 @@
1
+ // @vitest-environment happy-dom
2
+ import * as L from "leaflet";
3
+
4
+ const camelCase = (kebab) => kebab.replace(/-./g, (x) => x[1].toUpperCase());
5
+
6
+ class LIcon extends HTMLElement {
7
+ constructor() {
8
+ super();
9
+ this.icon = null;
10
+ }
11
+
12
+ connectedCallback() {
13
+ const options = {};
14
+
15
+ // Strings
16
+ let keys = [
17
+ "icon-url",
18
+ "icon-retina-url",
19
+ "shadow-url",
20
+ "shadow-retina-url",
21
+ "class-name",
22
+ ];
23
+ keys.forEach((key) => {
24
+ if (this.hasAttribute(key)) {
25
+ options[camelCase(key)] = this.getAttribute(key);
26
+ }
27
+ });
28
+
29
+ // Points
30
+ let points = [
31
+ "icon-anchor",
32
+ "icon-size",
33
+ "shadow-anchor",
34
+ "shadow-size",
35
+ "tooltip-anchor",
36
+ "popup-anchor",
37
+ ];
38
+ points.forEach((key) => {
39
+ if (this.hasAttribute(key)) {
40
+ options[camelCase(key)] = JSON.parse(this.getAttribute(key));
41
+ }
42
+ });
43
+
44
+ if (this.hasAttribute("cross-origin")) {
45
+ options.crossOrigin = this.getAttribute("cross-origin") === "true";
46
+ }
47
+ this.icon = L.icon(options);
48
+
49
+ const event = new CustomEvent("icon:add", {
50
+ cancelable: true,
51
+ bubbles: true,
52
+ detail: {
53
+ icon: this.icon,
54
+ },
55
+ });
56
+ this.dispatchEvent(event);
57
+ }
58
+ }
59
+
60
+ if (import.meta.vitest) {
61
+ const { it, expect, beforeAll } = import.meta.vitest;
62
+
63
+ beforeAll(() => {
64
+ customElements.define("l-icon", LIcon);
65
+ });
66
+
67
+ it("default", () => {
68
+ const el = document.createElement("l-icon");
69
+ document.body.appendChild(el);
70
+
71
+ let actual = el.icon;
72
+ let expected = L.icon();
73
+ expect(actual).toEqual(expected);
74
+ });
75
+
76
+ it("emits icon:add event", async () => {
77
+ const el = document.createElement("l-icon");
78
+ let promise = new Promise((resolve) => {
79
+ el.addEventListener("icon:add", (ev) => {
80
+ resolve(ev.detail.icon);
81
+ });
82
+ });
83
+ document.body.appendChild(el);
84
+ let actual = await promise;
85
+ let expected = L.icon();
86
+ expect(actual).toEqual(expected);
87
+ });
88
+
89
+ it("options", () => {
90
+ const el = document.createElement("l-icon");
91
+ el.setAttribute("icon-url", "url.png");
92
+ el.setAttribute("icon-retina-url", "retina.png");
93
+ el.setAttribute("icon-size", "[0, 0]");
94
+ el.setAttribute("icon-anchor", "[0, 0]");
95
+ el.setAttribute("popup-anchor", "[0, 0]");
96
+ el.setAttribute("tooltip-anchor", "[0, 0]");
97
+ el.setAttribute("shadow-url", "urlShadow.png");
98
+ el.setAttribute("shadow-retina-url", "retinaShadow.png");
99
+ el.setAttribute("shadow-size", "[0, 0]");
100
+ el.setAttribute("shadow-anchor", "[0, 0]");
101
+ el.setAttribute("class-name", "foo");
102
+ el.setAttribute("cross-origin", "true");
103
+ document.body.appendChild(el);
104
+
105
+ let actual = el.icon;
106
+ let expected = L.icon({
107
+ iconUrl: "url.png",
108
+ iconRetinaUrl: "retina.png",
109
+ iconSize: [0, 0],
110
+ iconAnchor: [0, 0],
111
+ popupAnchor: [0, 0],
112
+ tooltipAnchor: [0, 0],
113
+ shadowUrl: "urlShadow.png",
114
+ shadowRetinaUrl: "retinaShadow.png",
115
+ shadowSize: [0, 0],
116
+ shadowAnchor: [0, 0],
117
+ className: "foo",
118
+ crossOrigin: true,
119
+ });
120
+ expect(actual).toEqual(expected);
121
+ });
122
+ }
123
+
124
+ export default LIcon;
@@ -0,0 +1,43 @@
1
+ import { mapAddTo } from "./events.js";
2
+
3
+ class LImageOverlay extends HTMLElement {
4
+ static observedAttributes = ["url", "bounds", "opacity"];
5
+
6
+ constructor() {
7
+ super();
8
+ this.layer = null;
9
+ }
10
+
11
+ connectedCallback() {
12
+ const url = this.getAttribute("url");
13
+ const bounds = JSON.parse(this.getAttribute("bounds"));
14
+ const options = {
15
+ opacity: parseFloat(this.getAttribute("opacity") || "1.0"),
16
+ alt: this.getAttribute("alt") || "",
17
+ };
18
+ this.layer = L.imageOverlay(url, bounds, options);
19
+ this.dispatchEvent(
20
+ new CustomEvent(mapAddTo, {
21
+ cancelable: true,
22
+ bubbles: true,
23
+ detail: {
24
+ layer: this.layer,
25
+ },
26
+ }),
27
+ );
28
+ }
29
+
30
+ attributeChangedCallback(name, _oldValue, newValue) {
31
+ if (this.layer !== null) {
32
+ if (name === "url") {
33
+ this.layer.setUrl(newValue);
34
+ } else if (name === "bounds") {
35
+ this.layer.setBounds(JSON.parse(newValue));
36
+ } else if (name === "opacity") {
37
+ this.layer.setOpacity(parseFloat(newValue));
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ export default LImageOverlay;
@@ -0,0 +1,20 @@
1
+ class LLatLngBounds extends HTMLElement {
2
+ static observedAttributes = ["bounds"];
3
+
4
+ constructor() {
5
+ super();
6
+ }
7
+
8
+ attributeChangedCallback(_name, _oldValue, newValue) {
9
+ const event = new CustomEvent("map:bounds", {
10
+ bubbles: true,
11
+ detail: {
12
+ bounds: JSON.parse(newValue),
13
+ method: this.getAttribute("method") || "fitBounds",
14
+ },
15
+ });
16
+ this.dispatchEvent(event);
17
+ }
18
+ }
19
+
20
+ export default LLatLngBounds;
@@ -0,0 +1,39 @@
1
+ import { mapAddTo } from "./events.js";
2
+
3
+ class LLayerGroup extends HTMLElement {
4
+ constructor() {
5
+ super();
6
+ }
7
+
8
+ connectedCallback() {
9
+ const name = this.getAttribute("name");
10
+ const group = L.layerGroup();
11
+ const event = new CustomEvent(mapAddTo, {
12
+ cancelable: true,
13
+ bubbles: true,
14
+ detail: {
15
+ layer: group,
16
+ name,
17
+ },
18
+ });
19
+ this.dispatchEvent(event);
20
+
21
+ this.addEventListener(mapAddTo, (ev) => {
22
+ ev.stopPropagation();
23
+ group.addLayer(ev.detail.layer);
24
+ });
25
+
26
+ const observer = new MutationObserver(function (mutations) {
27
+ mutations.forEach((mutation) => {
28
+ mutation.removedNodes.forEach((node) => {
29
+ const leafletId = node.getAttribute("leaflet-id");
30
+ const layer = group.getLayer(leafletId);
31
+ group.removeLayer(layer);
32
+ });
33
+ });
34
+ });
35
+ observer.observe(this, { childList: true });
36
+ }
37
+ }
38
+
39
+ export default LLayerGroup;
package/src/l-map.js ADDED
@@ -0,0 +1,33 @@
1
+ // @ts-check
2
+ import { layerRemove, mapAddTo } from "./events.js";
3
+
4
+ class LMap extends HTMLElement {
5
+ constructor() {
6
+ super();
7
+
8
+ this.map = null;
9
+ this.addEventListener("map:bounds", (ev) => {
10
+ const { bounds, method } = ev.detail;
11
+ this.map[method](bounds);
12
+ });
13
+ }
14
+
15
+ connectedCallback() {
16
+ this.map = L.map(this);
17
+ const center = this.getAttribute("center");
18
+ const zoom = this.getAttribute("zoom");
19
+ if (center !== null && zoom !== null) {
20
+ this.map.setView(JSON.parse(center), parseInt(zoom));
21
+ }
22
+ this.addEventListener(mapAddTo, (ev) => {
23
+ const layer = ev.detail.layer;
24
+ layer.addTo(this.map);
25
+ });
26
+
27
+ this.addEventListener(layerRemove, (ev) => {
28
+ this.map.remove(ev.detail.layer);
29
+ });
30
+ }
31
+ }
32
+
33
+ export default LMap;
@@ -0,0 +1,94 @@
1
+ // @vitest-environment happy-dom
2
+ import * as L from "leaflet";
3
+ import { mapAddTo, popupAdd } from "./events.js";
4
+
5
+ class LMarker extends HTMLElement {
6
+ static observedAttributes = ["lat-lng", "opacity", "icon"];
7
+
8
+ constructor() {
9
+ super();
10
+ this.layer = null;
11
+ this.addEventListener("icon:add", (ev) => {
12
+ ev.stopPropagation();
13
+ this.layer.setIcon(ev.detail.icon);
14
+ })
15
+ }
16
+
17
+ connectedCallback() {
18
+ const latLng = JSON.parse(this.getAttribute("lat-lng"));
19
+ const opacity = parseFloat(this.getAttribute("opacity") || "1.0");
20
+ this.layer = L.marker(latLng, { opacity });
21
+ if (this.hasAttribute("icon")) {
22
+ const icon = L.icon(JSON.parse(this.getAttribute("icon")));
23
+ this.layer.setIcon(icon);
24
+ }
25
+
26
+ this.setAttribute("leaflet-id", L.stamp(this.layer));
27
+
28
+ this.addEventListener(popupAdd, (ev) => {
29
+ const { content } = ev.detail;
30
+ this.layer.bindPopup(content);
31
+ });
32
+
33
+ const event = new CustomEvent(mapAddTo, {
34
+ cancelable: true,
35
+ bubbles: true,
36
+ detail: {
37
+ layer: this.layer,
38
+ },
39
+ });
40
+ this.dispatchEvent(event);
41
+ }
42
+
43
+ attributeChangedCallback(name, _oldValue, newValue) {
44
+ if (this.layer !== null) {
45
+ if (name === "lat-lng") {
46
+ this.layer.setLatLng(JSON.parse(newValue));
47
+ }
48
+ if (name === "opacity") {
49
+ this.layer.setOpacity(parseFloat(newValue));
50
+ }
51
+ if (name === "icon") {
52
+ this.layer.setIcon(L.icon(JSON.parse(newValue)));
53
+ }
54
+ }
55
+ }
56
+ }
57
+
58
+ if (import.meta.vitest) {
59
+ const { it, expect, beforeAll } = import.meta.vitest;
60
+
61
+ beforeAll(() => {
62
+ customElements.define("l-marker", LMarker);
63
+ });
64
+
65
+ it("default icon", () => {
66
+ const el = document.createElement("l-marker");
67
+ document.body.appendChild(el);
68
+ let actual = el.layer.getIcon();
69
+ let expected = new L.Icon.Default();
70
+ expect(actual).toEqual(expected);
71
+ });
72
+
73
+ it("adds an icon", () => {
74
+ const el = document.createElement("l-marker");
75
+ // Set attribute before appendChild
76
+ el.setAttribute("icon", JSON.stringify({ iconUrl: "foo.png" }));
77
+ document.body.appendChild(el);
78
+ let actual = el.layer.getIcon();
79
+ let expected = L.icon({ iconUrl: "foo.png" });
80
+ expect(actual).toEqual(expected);
81
+ });
82
+
83
+ it("changes an icon", () => {
84
+ const el = document.createElement("l-marker");
85
+ // Set attribute after appendChild
86
+ document.body.appendChild(el);
87
+ el.setAttribute("icon", JSON.stringify({ iconUrl: "bar.png" }));
88
+ let actual = el.layer.getIcon();
89
+ let expected = L.icon({ iconUrl: "bar.png" });
90
+ expect(actual).toEqual(expected);
91
+ });
92
+ }
93
+
94
+ export default LMarker;
@@ -0,0 +1,15 @@
1
+ import { mapAddTo } from "./events.js";
2
+
3
+ class LOverlayLayers extends HTMLElement {
4
+ constructor() {
5
+ super();
6
+ }
7
+
8
+ connectedCallback() {
9
+ this.addEventListener(mapAddTo, (ev) => {
10
+ ev.detail["type"] = "overlay";
11
+ });
12
+ }
13
+ }
14
+
15
+ export default LOverlayLayers;
package/src/l-popup.js ADDED
@@ -0,0 +1,21 @@
1
+ import { popupAdd } from "./events.js";
2
+
3
+ class LPopup extends HTMLElement {
4
+ constructor() {
5
+ super();
6
+ }
7
+
8
+ connectedCallback() {
9
+ const content = this.getAttribute("content");
10
+ const event = new CustomEvent(popupAdd, {
11
+ cancelable: true,
12
+ bubbles: true,
13
+ detail: {
14
+ content,
15
+ },
16
+ });
17
+ this.dispatchEvent(event);
18
+ }
19
+ }
20
+
21
+ export default LPopup;
@@ -0,0 +1,22 @@
1
+ import { mapAddTo } from "./events.js";
2
+
3
+ class LTileLayer extends HTMLElement {
4
+ constructor() {
5
+ super();
6
+ }
7
+
8
+ connectedCallback() {
9
+ const name = this.getAttribute("name");
10
+ const urlTemplate = this.getAttribute("url-template");
11
+ const attribution = this.getAttribute("attribution");
12
+ const options = { attribution };
13
+ const layer = L.tileLayer(urlTemplate, options);
14
+ const event = new CustomEvent(mapAddTo, {
15
+ detail: { name, layer },
16
+ bubbles: true,
17
+ });
18
+ this.dispatchEvent(event);
19
+ }
20
+ }
21
+
22
+ export default LTileLayer;
@@ -0,0 +1,31 @@
1
+ import { mapAddTo } from "./events.js";
2
+
3
+ class LVideoOverlay extends HTMLElement {
4
+ constructor() {
5
+ super();
6
+ }
7
+
8
+ connectedCallback() {
9
+ const url = JSON.parse(this.getAttribute("url"));
10
+ const bounds = JSON.parse(this.getAttribute("bounds"));
11
+ const options = {
12
+ opacity: parseFloat(this.getAttribute("opacity") || "1.0"),
13
+ alt: this.getAttribute("alt") || "",
14
+ autoplay: true,
15
+ muted: true,
16
+ playsInline: true,
17
+ };
18
+ const layer = L.videoOverlay(url, bounds, options);
19
+ this.dispatchEvent(
20
+ new CustomEvent(mapAddTo, {
21
+ cancelable: true,
22
+ bubbles: true,
23
+ detail: {
24
+ layer,
25
+ },
26
+ }),
27
+ );
28
+ }
29
+ }
30
+
31
+ export default LVideoOverlay;
package/vite.config.js ADDED
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ includeSource: ["src/**/*.{js,ts}"],
6
+ },
7
+ });