diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 965fcf066..7c7462abf 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -209,13 +209,12 @@ export class MapViewer extends HTMLElement { // there is a better configuration than that, but at this moment // I'm not sure how to approach that issue. // See https://github.com/Maps4HTML/MapML-Leaflet-Client/issues/24 - fadeAnimation: true + fadeAnimation: true, + attributionControl: false }); this._addToHistory(); // the attribution control is not optional - this._attributionControl = this._map.attributionControl.setPrefix('Maps4HTML | Leaflet'); - this._attributionControl.getContainer().setAttribute("role","group"); - this._attributionControl.getContainer().setAttribute("aria-label","Map data attribution"); + this._attributionControl = M.attributionButton().addTo(this._map); this.setControls(false,false,true); this._crosshair = M.crosshair().addTo(this._map); diff --git a/src/mapml.css b/src/mapml.css index 3f23276ec..b1f67c6ac 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -140,13 +140,9 @@ .leaflet-tooltip-pane .leaflet-tooltip, .leaflet-touch .leaflet-control-layers, .leaflet-touch .leaflet-bar, -.leaflet-popup-tip { - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2), 0 1px 2px rgb(0, 0, 0, 0.3); -} - -/* Increase contrast between the attribution container and the underlying content. */ +.leaflet-popup-tip, .leaflet-container .leaflet-control-attribution { - background-color: rgba(255, 255, 255, 0.95); + box-shadow: rgb(0 0 0 / 30%) 0px 1px 4px -1px; } /* Remove opinionated styles of attribution links. */ @@ -155,6 +151,43 @@ text-decoration: revert; } + +/* + * Attribution. + */ + +.leaflet-container .leaflet-control-attribution { + background-color: #fff; + border-radius: 1.5em; + color: currentColor; + margin: 5px; + min-height: 30px; + min-width: 30px; + padding: 0; +} + +.leaflet-control-attribution summary { + display: block; + list-style: none; + border-radius: 100%; + position: absolute; + bottom: 0; + left: calc(100% - 30px); + line-height: 0; + width: 30px; + height: 30px; +} + +.leaflet-control-attribution summary svg { + border-radius: 100%; + width: inherit; + height: inherit; +} + +.mapml-attribution-container { + padding: 5px 35px 5px 10px; +} + /* * Zoom controls. */ diff --git a/src/mapml/control/AttributionButton.js b/src/mapml/control/AttributionButton.js new file mode 100644 index 000000000..969df8865 --- /dev/null +++ b/src/mapml/control/AttributionButton.js @@ -0,0 +1,110 @@ +export var AttributionButton = L.Control.extend({ + options: { + position: 'bottomright', + prefix: 'Maps4HTML Leaflet' + }, + + initialize: function (options) { + L.Util.setOptions(this, options); + this._attributions = {}; + }, + + onAdd: function (map) { + map.attributionControl = this; + this._container = L.DomUtil.create('details', 'leaflet-control-attribution'); + L.DomEvent.disableClickPropagation(this._container); + + for (var i in map._layers) { + if (map._layers[i].getAttribution) { + this.addAttribution(map._layers[i].getAttribution()); + } + } + + this._update(); + + map.on('layeradd', this._addAttribution, this); + + return this._container; + }, + + onRemove: function (map) { + map.off('layeradd', this._addAttribution, this); + }, + + _addAttribution: function (ev) { + if (ev.layer.getAttribution) { + this.addAttribution(ev.layer.getAttribution()); + ev.layer.once('remove', function () { + this.removeAttribution(ev.layer.getAttribution()); + }, this); + } + }, + + setPrefix: function (prefix) { + this.options.prefix = prefix; + this._update(); + return this; + }, + + addAttribution: function (text) { + if (!text) { return this; } + + if (!this._attributions[text]) { + this._attributions[text] = 0; + } + this._attributions[text]++; + + this._update(); + + return this; + }, + + removeAttribution: function (text) { + if (!text) { return this; } + + if (this._attributions[text]) { + this._attributions[text]--; + this._update(); + } + + return this; + }, + + _update: function () { + if (!this._map) { return; } + + var attribs = []; + + for (var i in this._attributions) { + if (this._attributions[i]) { + attribs.push(i); + } + } + + var prefixAndAttribs = []; + + if (this.options.prefix) { + prefixAndAttribs.push(this.options.prefix); + } + if (attribs.length) { + prefixAndAttribs.push(attribs.join(', ')); + } + + this._container.innerHTML = '' + '
' + prefixAndAttribs.join(' ') + '
'; + + } +}); + +L.Map.mergeOptions({ + attributionControl: true +}); + +L.Map.addInitHook(function () { + if (this.options.attributionControl) { + new AttributionButton().addTo(this); + } +}); + +export var attributionButton = function (options) { + return new AttributionButton(options); +}; diff --git a/src/mapml/index.js b/src/mapml/index.js index 6d587d814..c2de48a2c 100644 --- a/src/mapml/index.js +++ b/src/mapml/index.js @@ -51,6 +51,7 @@ import { DebugOverlay, debugOverlay} from './layers/DebugLayer'; import { QueryHandler } from './handlers/QueryHandler'; import { ContextMenu } from './handlers/ContextMenu'; import { Util } from './utils/Util'; +import { AttributionButton, attributionButton } from './control/AttributionButton'; import { ReloadButton, reloadButton } from './control/ReloadButton'; import { FullscreenButton, fullscreenButton } from './control/FullscreenButton'; import { Crosshair, crosshair } from "./layers/Crosshair"; @@ -640,6 +641,9 @@ M.mapMlFeatures = mapMlFeatures; M.MapMLLayerControl = MapMLLayerControl; M.mapMlLayerControl = mapMlLayerControl; +M.AttributionButton = AttributionButton; +M.attributionButton = attributionButton; + M.ReloadButton = ReloadButton; M.reloadButton = reloadButton; diff --git a/src/web-map.js b/src/web-map.js index 0b1bb1911..415038288 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -224,13 +224,12 @@ export class WebMap extends HTMLMapElement { // there is a better configuration than that, but at this moment // I'm not sure how to approach that issue. // See https://github.com/Maps4HTML/MapML-Leaflet-Client/issues/24 - fadeAnimation: true + fadeAnimation: true, + attributionControl: false }); this._addToHistory(); // the attribution control is not optional - this._attributionControl = this._map.attributionControl.setPrefix('Maps4HTML | Leaflet'); - this._attributionControl.getContainer().setAttribute("role","group"); - this._attributionControl.getContainer().setAttribute("aria-label","Map data attribution"); + this._attributionControl = M.attributionButton().addTo(this._map); this.setControls(false,false,true); this._crosshair = M.crosshair().addTo(this._map); diff --git a/test/e2e/core/mapElement.test.js b/test/e2e/core/mapElement.test.js index 9936c4d4b..3e5f77143 100644 --- a/test/e2e/core/mapElement.test.js +++ b/test/e2e/core/mapElement.test.js @@ -84,10 +84,4 @@ describe("Playwright Map Element Tests", () => { await expect(projection).toEqual("OSMTILE"); }); - test("Ensure attribution control has role='group' aria-label='Map data attribution'", async () => { - let role = await page.evaluate(`document.querySelector('map')._attributionControl.getContainer().getAttribute('role')`); - await expect(role).toEqual("group"); - let arialabel = await page.evaluate(`document.querySelector('map')._attributionControl.getContainer().getAttribute('aria-label')`); - await expect(arialabel).toEqual("Map data attribution"); - }); }); \ No newline at end of file diff --git a/test/e2e/mapml-viewer/mapml-viewer.test.js b/test/e2e/mapml-viewer/mapml-viewer.test.js index 4da2ce66f..785e82894 100644 --- a/test/e2e/mapml-viewer/mapml-viewer.test.js +++ b/test/e2e/mapml-viewer/mapml-viewer.test.js @@ -26,14 +26,6 @@ describe("Playwright mapml-viewer Element Tests", () => { afterAll(async function () { await context.close(); }); - - test("Ensure attribution control has role='group' aria-label='Map data attribution'", async () => { - let role = await page.evaluate(`document.querySelector('mapml-viewer')._attributionControl.getContainer().getAttribute('role')`); - await expect(role).toEqual("group"); - let arialabel = await page.evaluate(`document.querySelector('mapml-viewer')._attributionControl.getContainer().getAttribute('aria-label')`); - await expect(arialabel).toEqual("Map data attribution"); - }); - test("Initial map element extent", async () => { const extent = await page.$eval(