maplibre-preview 1.9.0 → 1.10.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2c43caaa2de2305aa8211b7d1de99aaf7337b6162d7d99a448f73007c717591a
4
- data.tar.gz: b16f170dc316e26844ffa6dd44d8401d38ba9128a9cf0dc13bd3abd1b30d0507
3
+ metadata.gz: d77238823bad1c9b83f993dab458e964ffd16fb6015f3506bbd62d86b690e131
4
+ data.tar.gz: 70b749547c4d2115fc20a0bde6c43ceb3303ddf1b22a1fc4a570e1ae12117552
5
5
  SHA512:
6
- metadata.gz: 2401ce59b8a156a2b34ddeecc36a3c6ad4ae161f41fcf998d3e2a6c788cf096f62fde591327948bdf18f1625b8eb9d0e55f226f8d758f7d6b0d8524087bc43f1
7
- data.tar.gz: ad6e8ab6f576675903edda6b0859df42ef467dffff70e651eaacb36bbb8c906806b7d3bfec2d29545bcad663bcc99a114d88b912edd22caf115d5b7ecbdada0f
6
+ metadata.gz: 0e64e1a5d7e2334ad705a18ccca590143d33c1c562094a0fb216aa2502b4b2666736633404e08d1233f5d260d978c7e8006a4f96e02122303199a066f2a8b20e
7
+ data.tar.gz: 51dbf7525797540e8de7ba77400d4d062bfb8cf2a6bbb1248b7c8e8b5027341825afe2654bc76bac64f5af6f6cc9e36b0d43b339534f6f7749b779a36c9867bd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.10.0] - 2026-05-20
4
+
5
+ ### Added
6
+ - **Host coordinate selection** - emit `maplibre-preview:coordinate-selected` with `lat` and `lon` on map clicks for embedded host applications
7
+
8
+ ### Changed
9
+ - **Embedded preview route** - pass configured preview options into the `/maplibre_preview` template route
10
+
11
+ ## [1.9.1] - 2026-05-15
12
+
13
+ ### Security
14
+ - **Feature popup tooltips** - render style and feature tooltip values as DOM text instead of raw HTML to prevent script execution from untrusted style or tile data
15
+
3
16
  ## [1.9.0] - 2026-05-14
4
17
 
5
18
  ### Added
@@ -1,3 +1,3 @@
1
1
  module MapLibrePreview
2
- VERSION = '1.9.0'
2
+ VERSION = '1.10.0'
3
3
  end
@@ -774,20 +774,50 @@ javascript:
774
774
  return modifiedStyle;
775
775
  };
776
776
 
777
- const popupFeature = (features, e, popup) => {
778
- const tt = (tooltip, feat) => tooltip?.replace(/\{([^}]+)\}/g, (match, prop) => {
779
- let value = feat;
780
- for (const p of prop.split('.')) {
781
- value = value?.[p];
782
- }
783
- return typeof value === 'object' ? JSON.stringify(value, null, 2) : (value || '');
777
+ const tooltipValue = (feat, prop) => {
778
+ let value = feat;
779
+ for (const p of prop.split('.')) {
780
+ value = value?.[p];
781
+ }
782
+ return typeof value === 'object' ? JSON.stringify(value, null, 2) : (value || '');
783
+ };
784
+
785
+ const tooltipText = (tooltip, feat) => tooltip?.replace(/\{([^}]+)\}/g, (match, prop) => tooltipValue(feat, prop));
786
+
787
+ const defaultTooltipText = (feat) => [
788
+ `"id": ${tooltipValue(feat, 'id')}`,
789
+ `"source": ${tooltipValue(feat, 'source')}`,
790
+ `"source-layer": ${tooltipValue(feat, 'sourceLayer')}`,
791
+ `"properties": ${tooltipValue(feat, 'properties')}`
792
+ ].join('\n');
793
+
794
+ const createPopupContent = (tooltips) => {
795
+ const fragment = document.createDocumentFragment();
796
+ tooltips.forEach((tooltip, index) => {
797
+ if (index > 0) fragment.appendChild(document.createElement('br'));
798
+ const item = document.createElement('pre');
799
+ item.textContent = tooltip;
800
+ fragment.appendChild(item);
784
801
  });
802
+ return fragment;
803
+ };
785
804
 
805
+ const popupFeature = (features, e, popup) => {
786
806
  const tooltips = features.map((feat) =>
787
- tt(feat.layer.metadata?.tooltip, feat) ||
788
- tt(`<pre>"id": {id},\n"source": {source},\n"source-layer": {sourceLayer},\n"properties": {properties}</pre>`, feat)
807
+ tooltipText(feat.layer.metadata?.tooltip, feat) || defaultTooltipText(feat)
789
808
  );
790
- popup.setLngLat(e.lngLat).setHTML(tooltips.join('<br>')).addTo(map);
809
+ popup.setLngLat(e.lngLat).setDOMContent(createPopupContent(tooltips)).addTo(map);
810
+ };
811
+
812
+ const emitCoordinateSelection = (lngLat) => {
813
+ const payload = {
814
+ type: 'maplibre-preview:coordinate-selected',
815
+ lat: lngLat.lat,
816
+ lon: lngLat.lng
817
+ };
818
+
819
+ window.dispatchEvent(new CustomEvent(payload.type, {detail: payload}));
820
+ window.parent !== window && window.parent.postMessage(payload, window.location.origin);
791
821
  };
792
822
 
793
823
  const initializeMap = async () => {
@@ -899,6 +929,8 @@ javascript:
899
929
  const popup = new maplibregl.Popup({closeButton: false, closeOnClick: false});
900
930
 
901
931
  map.on('click', function (e) {
932
+ emitCoordinateSelection(e.lngLat);
933
+
902
934
  if (profileMode) {
903
935
  handleProfileClick(e);
904
936
  return;
@@ -33,7 +33,7 @@ module MapLibrePreview
33
33
  app.set :maplibre_preview_options, {}
34
34
 
35
35
  app.get '/maplibre_preview' do
36
- slim :maplibre_map, layout: :maplibre_layout
36
+ slim :maplibre_map, layout: :maplibre_layout, locals: { options: settings.maplibre_preview_options }
37
37
  end
38
38
  end
39
39
  end
@@ -15,6 +15,7 @@ RSpec.describe MapLibrePreview do
15
15
  expect(last_response.body).to include('maplibre-contour')
16
16
  expect(last_response.body).to include('d3')
17
17
  expect(last_response.body).to include('overlay_layout')
18
+ expect(last_response.body).to include('maplibre-preview:coordinate-selected')
18
19
  end
19
20
 
20
21
  it 'renders map cache toggle wiring' do
@@ -81,6 +82,24 @@ RSpec.describe MapLibrePreview do
81
82
  expect(last_response.body).to include('window.toggleStyleParametersPanel')
82
83
  end
83
84
 
85
+ it 'renders feature popup tooltips as DOM text instead of raw HTML' do
86
+ get '/?style_url=https://example.com/style.json'
87
+ expect(last_response).to be_ok
88
+
89
+ expect(last_response.body).to include('createPopupContent')
90
+ expect(last_response.body).to include('item.textContent = tooltip')
91
+ expect(last_response.body).to include('setDOMContent(createPopupContent(tooltips))')
92
+ expect(last_response.body).not_to include('setHTML(tooltips')
93
+ end
94
+
95
+ it 'emits selected coordinates for host applications' do
96
+ get '/'
97
+ expect(last_response).to be_ok
98
+
99
+ expect(last_response.body).to include('emitCoordinateSelection(e.lngLat)')
100
+ expect(last_response.body).to include('window.parent.postMessage(payload, window.location.origin)')
101
+ end
102
+
84
103
  it 'serves all required JavaScript modules' do
85
104
  %w[/js/overlay_layout.js /js/filters.js /js/contour.js /js/tilegrid.js /js/temporal_picker.js /vendor/maplibre-gl/maplibre-gl.js /vendor/maplibre-contour/index.min.js /vendor/d3/d3.v7.min.js].each do |js_file|
86
105
  get js_file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maplibre-preview
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.9.0
4
+ version: 1.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Ludov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-14 00:00:00.000000000 Z
11
+ date: 2026-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack