@teipublisher/pb-components 1.30.4 → 1.32.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/css/leaflet/MarkerCluster.Default.css +60 -0
- package/css/leaflet/MarkerCluster.css +14 -0
- package/dist/demo/demos.json +3 -0
- package/dist/demo/pb-custom-form.html +61 -0
- package/dist/demo/pb-leaflet-map.html +9 -7
- package/dist/demo/pb-leaflet-map2.html +2 -2
- package/dist/demo/pb-leaflet-map3.html +3 -5
- package/dist/es-global-bridge-6abe3a88.js +5 -0
- package/dist/pb-components-bundle.js +126 -92
- package/dist/pb-elements.json +207 -5
- package/dist/pb-leaflet-map.js +4 -8
- package/lib/leaflet-src.js +14062 -0
- package/lib/leaflet.markercluster-src.js +2718 -0
- package/package.json +2 -1
- package/pb-elements.json +207 -5
- package/src/pb-components.js +1 -0
- package/src/pb-custom-form.js +49 -7
- package/src/pb-geolocation.js +18 -0
- package/src/pb-leaflet-map.js +113 -38
- package/src/pb-split-list.js +188 -0
package/src/pb-leaflet-map.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LitElement, html, css } from 'lit-element';
|
|
2
|
-
import
|
|
2
|
+
import "@lrnwebcomponents/es-global-bridge";
|
|
3
3
|
import { pbMixin } from './pb-mixin.js';
|
|
4
4
|
import { resolveURL } from './utils.js';
|
|
5
5
|
import './pb-map-layer.js';
|
|
@@ -9,9 +9,22 @@ import './pb-map-layer.js';
|
|
|
9
9
|
*
|
|
10
10
|
* @slot - may contain a series of `pb-map-layer` configurations
|
|
11
11
|
* @fires pb-leaflet-marker-click - Fires event to be processed by the map upon click
|
|
12
|
-
* @fires pb-update-map - When received, redraws the map to fit markers passed in with the event
|
|
13
|
-
*
|
|
14
|
-
* @fires pb-
|
|
12
|
+
* @fires pb-update-map - When received, redraws the map to fit markers passed in with the event.
|
|
13
|
+
* Event details should include an array of locations, see `pb-geolocation` event below.
|
|
14
|
+
* @fires pb-update - When received, redraws the map to show markers for all pb-geolocation elements found in the content of the pb-view
|
|
15
|
+
* @fires pb-geolocation - When received, focuses the map on the geocoordinates passed in with the event.
|
|
16
|
+
* The event details should include an object:
|
|
17
|
+
* ```
|
|
18
|
+
* {
|
|
19
|
+
* coordinates: {
|
|
20
|
+
* latitude: Number,
|
|
21
|
+
* longitude: Number
|
|
22
|
+
* },
|
|
23
|
+
* label: string - the label to show on mouseover,
|
|
24
|
+
* zoom: Number - fixed zoom level to zoom to,
|
|
25
|
+
* fitBounds: Boolean - if true, recompute current zoom level to show all markers
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
15
28
|
*/
|
|
16
29
|
export class PbLeafletMap extends pbMixin(LitElement) {
|
|
17
30
|
static get properties() {
|
|
@@ -29,6 +42,30 @@ export class PbLeafletMap extends pbMixin(LitElement) {
|
|
|
29
42
|
crs: {
|
|
30
43
|
type: String
|
|
31
44
|
},
|
|
45
|
+
/**
|
|
46
|
+
* If set, the map will automatically zoom so it can fit all the markers
|
|
47
|
+
*/
|
|
48
|
+
fitMarkers: {
|
|
49
|
+
type: Boolean,
|
|
50
|
+
attribute: 'fit-markers'
|
|
51
|
+
},
|
|
52
|
+
/**
|
|
53
|
+
* If set, combine markers into clusters if they are located too close together
|
|
54
|
+
* to display as single markers
|
|
55
|
+
*/
|
|
56
|
+
cluster: {
|
|
57
|
+
type: Boolean
|
|
58
|
+
},
|
|
59
|
+
/**
|
|
60
|
+
* Limits up to which zoom level markers are arranged into clusters.
|
|
61
|
+
* Using a higher zoom level here will result in more markers to be shown.
|
|
62
|
+
*
|
|
63
|
+
* Requires `cluster` option to be enabled.
|
|
64
|
+
*/
|
|
65
|
+
disableClusteringAt: {
|
|
66
|
+
type: Number,
|
|
67
|
+
attribute: 'disable-clustering-at'
|
|
68
|
+
},
|
|
32
69
|
/**
|
|
33
70
|
* If enabled, the map will not automatically scroll to the coordinates received via `pb-geolocation`
|
|
34
71
|
*/
|
|
@@ -73,6 +110,9 @@ export class PbLeafletMap extends pbMixin(LitElement) {
|
|
|
73
110
|
this.toggle = false;
|
|
74
111
|
this.noScroll = false;
|
|
75
112
|
this.disabled = true;
|
|
113
|
+
this.cluster = false;
|
|
114
|
+
this.fitMarkers = false;
|
|
115
|
+
this.disableClusteringAt = null;
|
|
76
116
|
}
|
|
77
117
|
|
|
78
118
|
connectedCallback() {
|
|
@@ -85,7 +125,8 @@ export class PbLeafletMap extends pbMixin(LitElement) {
|
|
|
85
125
|
* @param {{ detail: any[]; }} ev
|
|
86
126
|
*/
|
|
87
127
|
this.subscribeTo('pb-update-map', (ev) => {
|
|
88
|
-
|
|
128
|
+
this._markerLayer.clearLayers();
|
|
129
|
+
|
|
89
130
|
/**
|
|
90
131
|
* @param {{ latitude: any; longitude: any; label: any; }} loc
|
|
91
132
|
*/
|
|
@@ -101,14 +142,9 @@ export class PbLeafletMap extends pbMixin(LitElement) {
|
|
|
101
142
|
this.emitTo('pb-leaflet-marker-click', loc);
|
|
102
143
|
});
|
|
103
144
|
marker.bindTooltip(loc.label);
|
|
104
|
-
|
|
105
|
-
bounds.extend([loc.latitude, loc.longitude]);
|
|
145
|
+
this._markerLayer.addLayer(marker);
|
|
106
146
|
});
|
|
107
|
-
|
|
108
|
-
this._map.fitBounds(bounds);
|
|
109
|
-
} else {
|
|
110
|
-
this._map.setZoom(this.zoom);
|
|
111
|
-
}
|
|
147
|
+
this._fitBounds();
|
|
112
148
|
});
|
|
113
149
|
|
|
114
150
|
/**
|
|
@@ -117,20 +153,14 @@ export class PbLeafletMap extends pbMixin(LitElement) {
|
|
|
117
153
|
* @param {{ detail: { root: { querySelectorAll: (arg0: string) => any[]; }; }; }} ev
|
|
118
154
|
*/
|
|
119
155
|
this.subscribeTo('pb-update', (ev) => {
|
|
120
|
-
this.
|
|
121
|
-
if (layer instanceof L.Marker) {
|
|
122
|
-
layer.remove();
|
|
123
|
-
}
|
|
124
|
-
});
|
|
125
|
-
const bounds = L.latLngBounds();
|
|
156
|
+
this._markerLayer.clearLayers();
|
|
126
157
|
const locations = ev.detail.root.querySelectorAll('pb-geolocation');
|
|
127
158
|
/**
|
|
128
159
|
* @param {{ latitude: any; longitude: any; }} loc
|
|
129
160
|
*/
|
|
130
161
|
locations.forEach((loc) => {
|
|
131
162
|
const coords = L.latLng(loc.latitude, loc.longitude);
|
|
132
|
-
|
|
133
|
-
const marker = L.marker(coords).addTo(this._map);
|
|
163
|
+
const marker = L.marker(coords).addTo(this._markerLayer);
|
|
134
164
|
if (loc.label) {
|
|
135
165
|
marker.bindTooltip(loc.label);
|
|
136
166
|
}
|
|
@@ -141,12 +171,7 @@ export class PbLeafletMap extends pbMixin(LitElement) {
|
|
|
141
171
|
this.emitTo('pb-leaflet-marker-click', loc);
|
|
142
172
|
});
|
|
143
173
|
});
|
|
144
|
-
|
|
145
|
-
if (locations.length >= 1) {
|
|
146
|
-
this._map.fitBounds(bounds);
|
|
147
|
-
} else {
|
|
148
|
-
this._map.fitWorld();
|
|
149
|
-
}
|
|
174
|
+
this._fitBounds();
|
|
150
175
|
});
|
|
151
176
|
|
|
152
177
|
/**
|
|
@@ -169,7 +194,12 @@ export class PbLeafletMap extends pbMixin(LitElement) {
|
|
|
169
194
|
if (ev.detail.popup) {
|
|
170
195
|
marker.bindPopup(ev.detail.popup);
|
|
171
196
|
}
|
|
172
|
-
marker.addTo(this.
|
|
197
|
+
marker.addTo(this._markerLayer);
|
|
198
|
+
|
|
199
|
+
if (ev.detail.fitBounds) {
|
|
200
|
+
this._fitBounds();
|
|
201
|
+
}
|
|
202
|
+
|
|
173
203
|
console.log('<pb-leaflet-map> added marker');
|
|
174
204
|
} else {
|
|
175
205
|
console.log('<pb-leaflet-map> Marker already added to map');
|
|
@@ -177,7 +207,7 @@ export class PbLeafletMap extends pbMixin(LitElement) {
|
|
|
177
207
|
if (this.toggle) {
|
|
178
208
|
this.disabled = false;
|
|
179
209
|
}
|
|
180
|
-
this._locationChanged();
|
|
210
|
+
this._locationChanged(this.latitude, this.longitude, ev.detail.zoom);
|
|
181
211
|
}
|
|
182
212
|
});
|
|
183
213
|
}
|
|
@@ -186,13 +216,23 @@ export class PbLeafletMap extends pbMixin(LitElement) {
|
|
|
186
216
|
if (!this.toggle) {
|
|
187
217
|
this.disabled = false;
|
|
188
218
|
}
|
|
189
|
-
|
|
219
|
+
window.ESGlobalBridge.requestAvailability();
|
|
220
|
+
const leafletPath = resolveURL('../lib/leaflet-src.js');
|
|
221
|
+
const pluginPath = resolveURL('../lib/leaflet.markercluster-src.js');
|
|
222
|
+
window.ESGlobalBridge.instance.load("leaflet", leafletPath)
|
|
223
|
+
.then(() => window.ESGlobalBridge.instance.load("plugin", pluginPath));
|
|
224
|
+
window.addEventListener(
|
|
225
|
+
"es-bridge-plugin-loaded",
|
|
226
|
+
this._initMap.bind(this),
|
|
227
|
+
{ once: true }
|
|
228
|
+
);
|
|
190
229
|
}
|
|
191
230
|
|
|
192
231
|
render() {
|
|
193
232
|
const cssPath = resolveURL(this.cssPath);
|
|
194
233
|
return html`
|
|
195
234
|
<link rel="Stylesheet" href="${cssPath}/leaflet.css">
|
|
235
|
+
<link rel="Stylesheet" href="${cssPath}/MarkerCluster.Default.css">
|
|
196
236
|
<div id="map" style="height: 100%; width: 100%"></div>
|
|
197
237
|
`;
|
|
198
238
|
}
|
|
@@ -234,6 +274,18 @@ export class PbLeafletMap extends pbMixin(LitElement) {
|
|
|
234
274
|
crs
|
|
235
275
|
});
|
|
236
276
|
this._configureLayers();
|
|
277
|
+
|
|
278
|
+
if (this.cluster) {
|
|
279
|
+
const options = {};
|
|
280
|
+
if (this.disableClusteringAt) {
|
|
281
|
+
options.disableClusteringAtZoom = this.disableClusteringAt;
|
|
282
|
+
}
|
|
283
|
+
this._markerLayer = L.markerClusterGroup(options);
|
|
284
|
+
} else {
|
|
285
|
+
this._markerLayer = L.layerGroup();
|
|
286
|
+
}
|
|
287
|
+
this._markerLayer.addTo(this._map);
|
|
288
|
+
|
|
237
289
|
this.signalReady();
|
|
238
290
|
|
|
239
291
|
L.control.scale().addTo(this._map);
|
|
@@ -308,27 +360,50 @@ export class PbLeafletMap extends pbMixin(LitElement) {
|
|
|
308
360
|
}
|
|
309
361
|
}
|
|
310
362
|
|
|
311
|
-
|
|
363
|
+
_fitBounds() {
|
|
364
|
+
if (!this.fitMarkers) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const bounds = L.latLngBounds();
|
|
368
|
+
let len = 0;
|
|
369
|
+
this._markerLayer.eachLayer((layer) => {
|
|
370
|
+
bounds.extend(layer.getLatLng());
|
|
371
|
+
len += 1;
|
|
372
|
+
});
|
|
373
|
+
if (len === 0) {
|
|
374
|
+
this._map.fitWorld();
|
|
375
|
+
} else if (len === 1) {
|
|
376
|
+
this._map.fitBounds(bounds, {maxZoom: this.zoom});
|
|
377
|
+
} else {
|
|
378
|
+
this._map.fitBounds(bounds);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
_locationChanged(lat, long, zoom) {
|
|
312
383
|
if (this._map) {
|
|
313
|
-
const coords = L.latLng([
|
|
314
|
-
this.
|
|
315
|
-
if (layer
|
|
316
|
-
if (
|
|
384
|
+
const coords = L.latLng([lat, long]);
|
|
385
|
+
this._markerLayer.eachLayer((layer) => {
|
|
386
|
+
if (layer.getLatLng().equals(coords)) {
|
|
387
|
+
if (zoom && !this.noScroll) {
|
|
317
388
|
layer.openTooltip();
|
|
389
|
+
this._map.setView(coords, zoom);
|
|
390
|
+
} else if (this.cluster) {
|
|
391
|
+
this._markerLayer.zoomToShowLayer(layer, () =>
|
|
392
|
+
layer.openTooltip()
|
|
393
|
+
);
|
|
318
394
|
} else {
|
|
319
|
-
layer.
|
|
395
|
+
layer.openTooltip();
|
|
396
|
+
this._map.setView(coords, this.zoom);
|
|
320
397
|
}
|
|
321
398
|
}
|
|
322
399
|
});
|
|
323
|
-
if (!this.noScroll)
|
|
324
|
-
this._map.setView(coords, this.zoom);
|
|
325
400
|
}
|
|
326
401
|
}
|
|
327
402
|
|
|
328
403
|
_hasMarker(lat, long) {
|
|
329
404
|
const coords = L.latLng([lat, long]);
|
|
330
405
|
let found = false;
|
|
331
|
-
this.
|
|
406
|
+
this._markerLayer.eachLayer((layer) => {
|
|
332
407
|
if (layer instanceof L.Marker && layer.getLatLng().equals(coords)) {
|
|
333
408
|
found = true;
|
|
334
409
|
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit-element';
|
|
2
|
+
import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
|
3
|
+
import { pbMixin } from './pb-mixin.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Implements a list which is split into different categories
|
|
7
|
+
* (e.g. letters of the alphabet, countries ...).
|
|
8
|
+
* Only one category is shown at a time unless the server reports
|
|
9
|
+
* no categories (e.g. if the number of items to display goes below
|
|
10
|
+
* a defined threshold).
|
|
11
|
+
*
|
|
12
|
+
* The server-side API endpoint should return a JSON object with two
|
|
13
|
+
* properties:
|
|
14
|
+
*
|
|
15
|
+
* + `categories`: an array of category descriptions: each item should
|
|
16
|
+
* be an object with two properties: `category` - containing the name of the category
|
|
17
|
+
* and `count` - containing a count of items available under this category.
|
|
18
|
+
* + `items`: an array with the items to be shown for the currently selected
|
|
19
|
+
* category. Those may contain HTML markup.
|
|
20
|
+
*
|
|
21
|
+
* @cssprop --pb-categorized-list-columns - the number of columns to display (default: 2)
|
|
22
|
+
* @fires pb-submit - when received, submit a request to the server and refresh
|
|
23
|
+
* @fires pb-start-update - sent before the element sends the request to the server
|
|
24
|
+
* @fires pb-end-update - sent after new content has been received
|
|
25
|
+
*/
|
|
26
|
+
export class PbSplitList extends pbMixin(LitElement) {
|
|
27
|
+
static get properties() {
|
|
28
|
+
return {
|
|
29
|
+
/**
|
|
30
|
+
* Server-side API endpoint to retrieve items from
|
|
31
|
+
*/
|
|
32
|
+
url: {
|
|
33
|
+
type: String
|
|
34
|
+
},
|
|
35
|
+
/**
|
|
36
|
+
* The initially selected category
|
|
37
|
+
*/
|
|
38
|
+
selected: {
|
|
39
|
+
type: String
|
|
40
|
+
},
|
|
41
|
+
/**
|
|
42
|
+
* A CSS selector pointing to one or more `pb-custom-form`
|
|
43
|
+
* instances. The element will collect additional parameters
|
|
44
|
+
* from those forms and includes them in the request to the server
|
|
45
|
+
*/
|
|
46
|
+
subforms: {
|
|
47
|
+
type: String
|
|
48
|
+
},
|
|
49
|
+
_categories: {
|
|
50
|
+
type: Array
|
|
51
|
+
},
|
|
52
|
+
...super.properties
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
constructor() {
|
|
57
|
+
super();
|
|
58
|
+
this._categories = [];
|
|
59
|
+
this._params = {};
|
|
60
|
+
this.selected = null;
|
|
61
|
+
this.subforms = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
connectedCallback() {
|
|
65
|
+
super.connectedCallback();
|
|
66
|
+
|
|
67
|
+
this.selected = this.getParameter('category', this.selected);
|
|
68
|
+
|
|
69
|
+
window.addEventListener('popstate', (ev) => {
|
|
70
|
+
console.log('<pb-split-list> popstate: %o', ev);
|
|
71
|
+
this.selected = ev.state.category;
|
|
72
|
+
this.submit();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
this.subscribeTo('pb-submit', this.load.bind(this));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
firstUpdated() {
|
|
79
|
+
super.firstUpdated();
|
|
80
|
+
|
|
81
|
+
PbSplitList.waitOnce('pb-page-ready', () => {
|
|
82
|
+
this.load();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
submit() {
|
|
87
|
+
this.load();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
load() {
|
|
91
|
+
const formParams = this._paramsFromSubforms({ category: this.selected });
|
|
92
|
+
this.setParameters(formParams);
|
|
93
|
+
this.pushHistory('pb-split-list', formParams);
|
|
94
|
+
|
|
95
|
+
const params = new URLSearchParams(formParams);
|
|
96
|
+
|
|
97
|
+
const url = `${this.toAbsoluteURL(this.url)}?${params.toString()}`;
|
|
98
|
+
console.log(`<pb-split-list> Fetching from URL: ${url}`);
|
|
99
|
+
|
|
100
|
+
this.emitTo('pb-start-update');
|
|
101
|
+
|
|
102
|
+
fetch(url)
|
|
103
|
+
.then((response) => {
|
|
104
|
+
if (response.ok) {
|
|
105
|
+
return response.json();
|
|
106
|
+
}
|
|
107
|
+
return Promise.reject(response.status);
|
|
108
|
+
})
|
|
109
|
+
.then((json) => {
|
|
110
|
+
this._categories = json.categories;
|
|
111
|
+
this.innerHTML = json.items.join('');
|
|
112
|
+
this.emitTo('pb-end-update');
|
|
113
|
+
})
|
|
114
|
+
.catch((error) => {
|
|
115
|
+
console.error(`<pb-split-list> Error caught: ${error}`);
|
|
116
|
+
this.emitTo('pb-end-update');
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_selectCategory(ev, category) {
|
|
121
|
+
ev.preventDefault();
|
|
122
|
+
this.selected = category;
|
|
123
|
+
this.load();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_paramsFromSubforms(params) {
|
|
127
|
+
if (this.subforms) {
|
|
128
|
+
document.querySelectorAll(this.subforms).forEach((form) => {
|
|
129
|
+
if (form.serializeForm) {
|
|
130
|
+
Object.assign(params, form.serializeForm());
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return params;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
render() {
|
|
138
|
+
return html`
|
|
139
|
+
<header>
|
|
140
|
+
${
|
|
141
|
+
this._categories.map((cat) =>
|
|
142
|
+
html`
|
|
143
|
+
<a part="${this.selected === cat.category ? 'active-category' : 'category'}" href="#${cat.category}" title="${cat.count}" class="${this.selected === cat.category ? 'active' : ''}"
|
|
144
|
+
@click="${(ev) => this._selectCategory(ev, cat.category)}">
|
|
145
|
+
${cat.category}
|
|
146
|
+
</a>
|
|
147
|
+
`
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
</header>
|
|
151
|
+
<div id="items" part="items"><slot></slot></div>
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static get styles() {
|
|
156
|
+
return css`
|
|
157
|
+
:host {
|
|
158
|
+
display: block;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
header {
|
|
162
|
+
display: flex;
|
|
163
|
+
flex-wrap: wrap;
|
|
164
|
+
column-gap: 10px;
|
|
165
|
+
width: 100%;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
#items {
|
|
169
|
+
display: grid;
|
|
170
|
+
grid-template-columns: repeat(var(--pb-categorized-list-columns, 2), auto);
|
|
171
|
+
grid-auto-rows: 1fr;
|
|
172
|
+
column-gap: 10px;
|
|
173
|
+
width: 100%;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
[part=category], #items a {
|
|
177
|
+
text-decoration: none;
|
|
178
|
+
color: var(--pb-link-color);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
[part=active-category] {
|
|
182
|
+
text-decoration: none;
|
|
183
|
+
color: var(--pb-highlight-color);
|
|
184
|
+
}
|
|
185
|
+
`;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
customElements.define('pb-split-list', PbSplitList);
|