Skip to content

Commit

Permalink
Malvoz attribution control (#815)
Browse files Browse the repository at this point in the history
* Collapse attribution behind a button

* Update attribution

* Change the attribution control SVG to dark on light

* attribution control

* tests fix

* Aliyan suggested changes

* deleted comments

* test fix

* one flaky test

* Restore and fix old test

* Peter suggested changes

* string localized

* Get rid of unused file, formatting

* Make sure aria-label has localized string, get rid of title to eliminate
duplicate announcing

---------

Co-authored-by: Malvoz <26493779+Malvoz@users.noreply.github.com>
Co-authored-by: Jacky0299 <jackyissuperman@gmail.com>
Co-authored-by: Jacky0299 <83443908+Jacky0299@users.noreply.github.com>
Co-authored-by: prushfor <prushfor@L-BSC-A146390.nrn.nrcan.gc.ca>
  • Loading branch information
5 people committed Apr 17, 2023
1 parent 8370cac commit fc0c8da
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 43 deletions.
2 changes: 0 additions & 2 deletions src/mapml-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,6 @@ export class MapViewer extends HTMLElement {
fadeAnimation: true
});
this._addToHistory();
// the attribution control is not optional
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 @@ -153,13 +153,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 @@ -168,6 +164,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
73 changes: 73 additions & 0 deletions src/mapml/control/AttributionButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
export var AttributionButton = L.Control.Attribution.extend({
options: {
prefix: '<a href="https://www.w3.org/community/maps4html/">Maps for HTML Community Group</a> | <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> '
},

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;
},

_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><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> | ` + prefixAndAttribs.join(' <span aria-hidden="true">|</span> ') + '</div>';
this._container.setAttribute("role","group");
this._container.setAttribute("aria-label",`${M.options.locale.btnAttribution}`);
}
});

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);
};
17 changes: 0 additions & 17 deletions src/mapml/control/AttributionControl.js

This file was deleted.

13 changes: 7 additions & 6 deletions src/mapml/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,20 +51,20 @@ 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 { ScaleBar, scaleBar } from './control/ScaleBar';
import { FullscreenButton, fullscreenButton } from './control/FullscreenButton';
import { attributionControl } from "./control/AttributionControl";
import {geolocationButton} from "./control/GeolocationButton";
import { geolocationButton } from "./control/GeolocationButton";
import { Crosshair, crosshair } from "./layers/Crosshair";
import { Feature, feature } from "./features/feature";
import { FeatureRenderer, featureRenderer } from './features/featureRenderer';
import { FeatureGroup, featureGroup} from './features/featureGroup';
import {AnnounceMovement} from "./handlers/AnnounceMovement";
import { AnnounceMovement } from "./handlers/AnnounceMovement";
import { FeatureIndex } from "./handlers/FeatureIndex";
import { Options } from "./options";
import "./keyboard";
import {featureIndexOverlay, FeatureIndexOverlay} from "./layers/FeatureIndexOverlay";
import { featureIndexOverlay, FeatureIndexOverlay } from "./layers/FeatureIndexOverlay";

/* global L, Node */
(function (window, document, undefined) {
Expand Down Expand Up @@ -653,6 +653,9 @@ M.featureLayer = featureLayer;
M.LayerControl = LayerControl;
M.layerControl = layerControl;

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

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

Expand All @@ -662,8 +665,6 @@ M.scaleBar = scaleBar;
M.FullscreenButton = FullscreenButton;
M.fullscreenButton = fullscreenButton;

M.attributionControl = attributionControl;

M.geolocationButton = geolocationButton;

M.StaticTileLayer = StaticTileLayer;
Expand Down
1 change: 1 addition & 0 deletions src/mapml/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export var Options = {
lmZoomToLayer: "Zoom To Layer",
lmCopyLayer: "Copy Layer",
lcOpacity: "Opacity",
btnAttribution: "Map data attribution",
btnZoomIn: "Zoom in",
btnZoomOut: "Zoom out",
btnFullScreen: "View Fullscreen",
Expand Down
2 changes: 0 additions & 2 deletions src/web-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -275,8 +275,6 @@ export class WebMap extends HTMLMapElement {
fadeAnimation: true
});
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: 3 additions & 3 deletions test/e2e/core/mapElement.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ 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')`);
let role = await page.evaluate(`document.querySelector('map')._map.attributionControl.getContainer().getAttribute('role')`);
await expect(role).toEqual("group");
let arialabel = await page.evaluate(`document.querySelector('map')._attributionControl.getContainer().getAttribute('aria-label')`);
let arialabel = await page.evaluate(`document.querySelector('map')._map.attributionControl.getContainer().getAttribute('aria-label')`);
await expect(arialabel).toEqual("Map data attribution");
});
});
});
18 changes: 13 additions & 5 deletions test/e2e/core/popupTabNavigation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,17 @@ test.describe("Playwright Keyboard Navigation + Query Layer Tests" , () => {
});

test("Focus Controls focuses the first <button> child in control div", async () => {
await page.waitForTimeout(1000);
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("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 @@ -201,11 +212,8 @@ test.describe("Playwright Keyboard Navigation + Query Layer Tests" , () => {
for (let i = 0; i < 5; i++)
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
const h = await page.evaluateHandle(() => document.querySelector("mapml-viewer"));
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");
let f = await page.$eval("body > mapml-viewer", (viewer) => viewer.shadowRoot.activeElement.innerHTML);
expect(f).toEqual("Maps for HTML Community Group");
});
});
});
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");
});

Expand Down

0 comments on commit fc0c8da

Please sign in to comment.