Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Malvoz attribution control #815

Merged
merged 16 commits into from
Apr 17, 2023
Merged
1 change: 0 additions & 1 deletion src/mapml-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,6 @@ export class MapViewer extends HTMLElement {
});
this._addToHistory();
// the attribution control is not optional
AliyanH marked this conversation as resolved.
Show resolved Hide resolved
M.attributionControl(this);

this._createControls();
this._toggleControls();
Expand Down
45 changes: 39 additions & 6 deletions src/mapml.css
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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.
*/
Expand Down
123 changes: 123 additions & 0 deletions src/mapml/control/AttributionButton.js
prushforth marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
export var AttributionButton = L.Control.Attribution.extend({
options: {
position: 'bottomright',
prefix: '<a href="https://www.w3.org/community/maps4html/">Maps for HTML Community Group</a> '
},

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);

let dialog = document.createElement("dialog");
dialog.setAttribute("class", "shortcuts-dialog");
dialog.setAttribute("autofocus", "");
dialog.onclick = function (e) {
e.stopPropagation();
};
dialog.innerHTML = `<b>${M.options.locale.kbdShortcuts} </b><button aria-label="Close" onclick='this.parentElement.close()'>X</button>` +
`<ul><b>${M.options.locale.kbdMovement}</b><li><kbd>&#8593</kbd> ${M.options.locale.kbdPanUp}</li><li><kbd>&#8595</kbd> ${M.options.locale.kbdPanDown}</li><li><kbd>&#8592</kbd> ${M.options.locale.kbdPanLeft}</li><li><kbd>&#8594</kbd> ${M.options.locale.kbdPanRight}</li><li><kbd>+</kbd> ${M.options.locale.btnZoomIn}</li><li><kbd>-</kbd> ${M.options.locale.btnZoomOut}</li><li><kbd>shift</kbd> + <kbd>&#8592/&#8593/&#8594/&#8595</kbd> 3x ${M.options.locale.kbdPanIncrement}</li><li><kbd>ctrl</kbd> + <kbd>&#8592/&#8593/&#8594/&#8595</kbd> 0.2x ${M.options.locale.kbdPanIncrement}</li><li><kbd>shift</kbd> + <kbd>+/-</kbd> ${M.options.locale.kbdZoom}</li></ul>` +
`<ul><b>${M.options.locale.kbdFeature}</b><li><kbd>&#8592/&#8593</kbd> ${M.options.locale.kbdPrevFeature}</li><li><kbd>&#8594/&#8595</kbd> ${M.options.locale.kbdNextFeature}</li></ul>`;
map._container.appendChild(dialog);

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 = '<summary title="Map data attribution"><svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" height="30px" viewBox="0 0 24 24" width="30px" fill="currentColor"><path d="M0 0h24v24H0V0z" fill="none"></path><path d="M11 7h2v2h-2zm0 4h2v6h-2zm1-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></svg></summary>' + '<div class="mapml-attribution-container">' + `<button onclick="this.closest(\'.leaflet-container\').querySelector(\'.shortcuts-dialog\').showModal()" class="shortcuts-button mapml-button">${M.options.locale.kbdShortcuts}</button> | <img src="data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMTIiIGhlaWdodD0iOCI+PHBhdGggZmlsbD0iIzRDN0JFMSIgZD0iTTAgMGgxMnY0SDB6Ii8+PHBhdGggZmlsbD0iI0ZGRDUwMCIgZD0iTTAgNGgxMnYzSDB6Ii8+PHBhdGggZmlsbD0iI0UwQkMwMCIgZD0iTTAgN2gxMnYxSDB6Ii8+PC9zdmc+" style="padding-right: 0.3em;" alt="Slava Ukraini"> <a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a> | ` + prefixAndAttribs.join(' <span aria-hidden="true">|</span> ') + '</div>';
this._container.setAttribute("role","group");
this._container.setAttribute("aria-label","Map data attribution");
}
});

L.Map.mergeOptions({
attributionControl: false,
toggleableAttributionControl: true,
});

L.Map.addInitHook(function () {
if (this.options.toggleableAttributionControl) {
new AttributionButton().addTo(this);
}
});

export var attributionButton = function (options) {
return new AttributionButton(options);
};
30 changes: 15 additions & 15 deletions src/mapml/control/AttributionControl.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions src/mapml/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ import { DebugOverlay, debugOverlay} from './layers/DebugOverlay';
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 {attributionControl} from "./control/AttributionControl";
// import {attributionControl} from "./control/AttributionControl";
import {geolocationButton} from "./control/GeolocationButton";
import { Crosshair, crosshair } from "./layers/Crosshair";
import { Feature, feature } from "./features/feature";
Expand Down Expand Up @@ -652,13 +653,16 @@ M.featureLayer = featureLayer;
M.LayerControl = LayerControl;
M.layerControl = layerControl;

M.AttributionButton = AttributionButton;
M.attributionButton = attributionButton;

M.ReloadButton = ReloadButton;
M.reloadButton = reloadButton;

M.FullscreenButton = FullscreenButton;
M.fullscreenButton = fullscreenButton;

M.attributionControl = attributionControl;
// M.attributionControl = attributionControl;

M.geolocationButton = geolocationButton;

Expand Down
1 change: 0 additions & 1 deletion src/web-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,6 @@ export class WebMap extends HTMLMapElement {
});
this._addToHistory();
// the attribution control is not optional
M.attributionControl(this);

this._createControls();
this._toggleControls();
Expand Down
2 changes: 2 additions & 0 deletions test/e2e/core/kbdAttribution.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ test.describe("Keyboard shortcut attribution test", ()=> {
await page.keyboard.press("Tab");
}

await page.keyboard.press("Enter");
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
const dialog = await page.$eval("body > mapml-viewer div > dialog",
(dialog) => dialog.hasAttribute("open")
Expand Down
6 changes: 0 additions & 6 deletions test/e2e/core/mapElement.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,4 @@ test.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");
});
});
12 changes: 11 additions & 1 deletion test/e2e/core/popupTabNavigation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,16 @@ test.describe("Playwright Keyboard Navigation + Query Layer Tests" , () => {
});

test("Focus Controls focuses the first <button> child in control div", async () => {
await page.pause();
await page.click("body > mapml-viewer");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
await page.click("body > mapml-viewer");
await page.keyboard.press("Shift+F10");
await page.keyboard.press("t");
Expand All @@ -205,7 +215,7 @@ test.describe("Playwright Keyboard Navigation + Query Layer Tests" , () => {
const nh = await page.evaluateHandle(doc => doc.shadowRoot, h);
const rh = await page.evaluateHandle(root => root.activeElement, nh);
const f = await (await page.evaluateHandle(elem => elem.innerText, rh)).jsonValue();
expect(f).toEqual("Maps4HTML");
expect(f).toEqual("Leaflet");
AliyanH marked this conversation as resolved.
Show resolved Hide resolved
});
});
});
4 changes: 2 additions & 2 deletions test/e2e/mapml-viewer/mapml-viewer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ test.describe("Playwright mapml-viewer Element Tests", () => {
});

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')`);
let role = await page.evaluate(`document.querySelector('mapml-viewer')._map.attributionControl._container.getAttribute('role')`);
expect(role).toEqual("group");
let arialabel = await page.evaluate(`document.querySelector('mapml-viewer')._attributionControl.getContainer().getAttribute('aria-label')`);
let arialabel = await page.evaluate(`document.querySelector('mapml-viewer')._map.attributionControl._container.getAttribute('aria-label')`);
expect(arialabel).toEqual("Map data attribution");
prushforth marked this conversation as resolved.
Show resolved Hide resolved
});

Expand Down