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

Follows updated keyboard interaction rules #292

Merged
merged 7 commits into from
Feb 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/mapml.css
Original file line number Diff line number Diff line change
Expand Up @@ -391,9 +391,9 @@ summary {
}

.mapml-crosshair {
margin: -18px 0 0 -18px;
width: 36px;
height: 36px;
margin: -36px 0 0 -36px;
width: 72px;
height: 72px;
left: 50%;
top: 50%;
content: '';
Expand Down
6 changes: 5 additions & 1 deletion src/mapml/handlers/QueryHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ export var QueryHandler = L.Handler.extend({
}
},
_queryTopLayerAtMapCenter: function (event) {
if (event.originalEvent.key === " ") {
setTimeout(() => {
if (this._map.isFocused && !this._map._popupClosed && (event.originalEvent.key === " " || +event.originalEvent.keyCode === 13)) {
this._map.fire('click', {
latlng: this._map.getCenter(),
layerPoint: this._map.latLngToLayerPoint(this._map.getCenter()),
containerPoint: this._map.latLngToContainerPoint(this._map.getCenter())
});
} else {
delete this._map._popupClosed;
}
}, 0);
},
_queryTopLayer: function(event) {
var layer = this._getTopQueryableLayer();
Expand Down
34 changes: 17 additions & 17 deletions src/mapml/layers/Crosshair.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,22 @@ export var Crosshair = L.Layer.extend({

this._container = L.DomUtil.create("div", "mapml-crosshair", map._container);
this._container.innerHTML = svgInnerHTML;
this._mapFocused = false;
map.isFocused = false;
this._isQueryable = false;

map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this);
L.DomEvent.on(map._container, "keydown keyup mousedown", this._onKey, this);

map.on("popupopen", this._isMapFocused, this);
L.DomEvent.on(map._container, "keydown keyup mousedown", this._isMapFocused, this);

this._addOrRemoveCrosshair();
},

onRemove: function (map) {
map.off("layerchange layeradd layerremove overlayremove", this._toggleEvents);
map.off("popupopen", this._isMapFocused);
L.DomEvent.off(map._container, "keydown keyup mousedown", this._isMapFocused);
},

_toggleEvents: function () {
if (this._hasQueryableLayer()) {
this._map.on("viewreset move moveend", this._addOrRemoveCrosshair, this);
Expand All @@ -59,7 +65,7 @@ export var Crosshair = L.Layer.extend({

_hasQueryableLayer: function () {
let layers = this._map.options.mapEl.layers;
if (this._mapFocused) {
if (this._map.isFocused) {
for (let layer of layers) {
if (layer.checked && layer._layer.queryable) {
return true;
Expand All @@ -69,20 +75,14 @@ export var Crosshair = L.Layer.extend({
return false;
},

_onKey: function (e) {
//set mapFocused = true if arrow buttons are used
if (["keydown", "keyup"].includes(e.type) && e.target.classList.contains("leaflet-container") && [32, 37, 38, 39, 40, 187, 189].includes(+e.keyCode)) {
this._mapFocused = true;
//set mapFocused = true if map is focued using tab
} else if (e.type === "keyup" && e.target.classList.contains("leaflet-container") && +e.keyCode === 9) {
this._mapFocused = true;
// set mapFocused = false and close all popups if tab or escape is used
} else if((e.type === "keyup" && e.target.classList.contains("leaflet-interactive") && +e.keyCode === 9) || +e.keyCode === 27){
this._mapFocused = false;
this._map.closePopup();
// set mapFocused = false if any other key is pressed
_isMapFocused: function (e) {
//set this._map.isFocused = true if arrow buttons are used
if (this._map._container.parentNode.activeElement.classList.contains("leaflet-container") && ["keydown"].includes(e.type) && (e.shiftKey && e.keyCode === 9)) {
this._map.isFocused = false;
} else if (this._map._container.parentNode.activeElement.classList.contains("leaflet-container") && ["keyup", "keydown"].includes(e.type)) {
this._map.isFocused = true;
} else {
this._mapFocused = false;
this._map.isFocused = false;
}
this._addOrRemoveCrosshair();
},
Expand Down
9 changes: 9 additions & 0 deletions src/mapml/layers/FeatureLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,10 @@ export var MapMLFeatures = L.FeatureGroup.extend({

if (options.onEachFeature) {
options.onEachFeature(layer.properties, layer);
layer._events.keypress.push({
"ctx": layer,
"fn": this._onSpacePress,
});
}
if(this._staticFeature){
let featureZoom = mapml.getAttribute('zoom') || nativeZoom;
Expand Down Expand Up @@ -324,6 +328,11 @@ export var MapMLFeatures = L.FeatureGroup.extend({
for(let i = 0; i < toDelete.length;i++){
this._container.removeChild(toDelete[i]);
}
},
_onSpacePress: function(e){
if(e.originalEvent.keyCode === 32){
this._openPopup(e);
}
},
geometryToLayer: function (mapml, pointToLayer, coordsToLatLng, vectorOptions, nativeCS, zoom) {
var geometry = mapml.tagName.toUpperCase() === 'FEATURE' ? mapml.getElementsByTagName('geometry')[0] : mapml,
Expand Down
58 changes: 45 additions & 13 deletions src/mapml/layers/MapLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ export var MapMLLayer = L.Layer.extend({
var c = document.createElement('div');
c.classList.add("mapml-popup-content");
c.insertAdjacentHTML('afterbegin', properties.innerHTML);
geometry.bindPopup(c, {autoPan:false, closeButton: false, minWidth: 108});
geometry.bindPopup(c, {autoPan:false, minWidth: 108});
}
}
});
Expand Down Expand Up @@ -125,7 +125,7 @@ export var MapMLLayer = L.Layer.extend({
var c = document.createElement('div');
c.classList.add("mapml-popup-content");
c.insertAdjacentHTML('afterbegin', properties.innerHTML);
geometry.bindPopup(c, {autoPan:false, closeButton: false, minWidth: 108});
geometry.bindPopup(c, {autoPan:false, minWidth: 108});
}
}
}).addTo(map);
Expand Down Expand Up @@ -1162,8 +1162,7 @@ export var MapMLLayer = L.Layer.extend({
content = popup._container.getElementsByClassName("mapml-popup-content")[0];

content.setAttribute("tabindex", "-1");
content.focus();
popup._count = 0;
popup._count = 0; // used for feature pagination

if(popup._source._eventParents){ // check if the popup is for a feature or query
layer = popup._source._eventParents[Object.keys(popup._source._eventParents)[0]]; // get first parent of feature, there should only be one
Expand Down Expand Up @@ -1232,7 +1231,11 @@ export var MapMLLayer = L.Layer.extend({
L.DomEvent.on(controlFocusButton, 'click', L.DomEvent.stop);
L.DomEvent.on(controlFocusButton, 'click', (e) => {
map.closePopup();
map._controlContainer.focus();
if(map._controlContainer.firstElementChild.firstElementChild.firstElementChild){
map._controlContainer.firstElementChild.firstElementChild.firstElementChild.focus();
} else {
map._controlContainer.focus();
}
}, popup);

let divider = L.DomUtil.create("hr");
Expand All @@ -1241,31 +1244,60 @@ export var MapMLLayer = L.Layer.extend({
popup._navigationBar = div;
popup._content.appendChild(divider);
popup._content.appendChild(div);


content.focus();

if(path) {
// e.target = this._map
// Looks for keydown, more specifically tab and shift tab
map.on("keydown", focusFeature);
// if popup closes then the focusFeature handler can be removed
map.on("popupclose", removeHandlers);
} else {
map.on("keydown", focusMap);
}
// When popup is open, what gets focused with tab needs to be done using JS as the DOM order is not in an accessibility friendly manner
function focusFeature(focusEvent){
if(focusEvent.originalEvent.path[0].title==="Focus Controls" && +focusEvent.originalEvent.keyCode === 9){
let isTab = focusEvent.originalEvent.keyCode === 9,
shiftPressed = focusEvent.originalEvent.shiftKey;
if((focusEvent.originalEvent.path[0].classList.contains("leaflet-popup-close-button") && isTab && !shiftPressed) || focusEvent.originalEvent.keyCode === 27){
L.DomEvent.stop(focusEvent);
path.focus();
} else if(focusEvent.originalEvent.shiftKey && +focusEvent.originalEvent.keyCode === 9){
map.closePopup(popup);
L.DomEvent.stop(focusEvent);
path.focus();
} else if ((focusEvent.originalEvent.path[0].title==="Focus Map" || focusEvent.originalEvent.path[0].classList.contains("mapml-popup-content")) && isTab && shiftPressed){
setTimeout(() => { //timeout needed so focus of the feature is done even after the keypressup event occurs
L.DomEvent.stop(focusEvent);
map.closePopup(popup);
path.focus();
}, 0);
}
}

function focusMap(focusEvent){
let isTab = focusEvent.originalEvent.keyCode === 9,
shiftPressed = focusEvent.originalEvent.shiftKey;

if((focusEvent.originalEvent.keyCode === 13 && focusEvent.originalEvent.path[0].classList.contains("leaflet-popup-close-button")) || focusEvent.originalEvent.keyCode === 27 ){
L.DomEvent.stopPropagation(focusEvent);
map._container.focus();
map.closePopup(popup);
if(focusEvent.originalEvent.keyCode !== 27)map._popupClosed = true;
} else if (isTab && focusEvent.originalEvent.path[0].classList.contains("leaflet-popup-close-button")){
map.closePopup(popup);
} else if ((focusEvent.originalEvent.path[0].title==="Focus Map" || focusEvent.originalEvent.path[0].classList.contains("mapml-popup-content")) && isTab && shiftPressed){
setTimeout(() => { //timeout needed so focus of the feature is done even after the keypressup event occurs
L.DomEvent.stop(focusEvent);
map.closePopup(popup);
map._container.focus();
}, 0);
}
}

// if popup closes then the focusFeature handler can be removed
map.on("popupclose", removeHandlers);
function removeHandlers(removeEvent){
if (removeEvent.popup === popup){
map.off("keydown", focusFeature);
map.off("popupclose", removeHandlers);
map.off("keydown", focusMap);
map.off('popupclose', removeHandlers);
}
}
},
Expand Down
24 changes: 15 additions & 9 deletions test/e2e/core/keyboardInteraction.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jest.setTimeout(50000);
expect(afterTab).toEqual("");
});

test("[" + browserType + "]" + " Crosshair remains on map move with arrow keys + space", async () => {
test("[" + browserType + "]" + " Crosshair remains on map move with arrow keys", async () => {
await page.keyboard.press("ArrowUp");
await page.waitForTimeout(500);
await page.keyboard.press("ArrowDown");
Expand All @@ -39,13 +39,11 @@ jest.setTimeout(50000);
await page.waitForTimeout(500);
await page.keyboard.press("ArrowRight");
await page.waitForTimeout(500);
await page.keyboard.press(" ")
await page.waitForTimeout(500);
const afterMove = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility);
expect(afterMove).toEqual("");
});

test("[" + browserType + "]" + " Crosshair hidden on esc + tab out", async () => {
test("[" + browserType + "]" + " Crosshair shows on esc but hidden on tab out", async () => {
await page.keyboard.press("Escape");
const afterEsc = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility);
await page.click("body");
Expand All @@ -55,7 +53,7 @@ jest.setTimeout(50000);
await page.keyboard.press("Tab");
const afterTab = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility);

expect(afterEsc).toEqual("hidden");
expect(afterEsc).toEqual("");
expect(afterTab).toEqual("hidden");
});

Expand Down Expand Up @@ -164,16 +162,24 @@ jest.setTimeout(50000);
const rh6 = await page.evaluateHandle(root => root.activeElement, nh6);
const f6 = await (await page.evaluateHandle(elem => elem.title, rh6)).jsonValue();

await page.keyboard.press("Tab");
const h7 = await page.evaluateHandle(() => document.querySelector("mapml-viewer"));
const nh7 = await page.evaluateHandle(doc => doc.shadowRoot, h7);
const rh7 = await page.evaluateHandle(root => root.activeElement, nh7);
const f7 = await (await page.evaluateHandle(elem => elem.className, rh7)).jsonValue();

expect(f).toEqual("mapml-popup-content");
expect(f2).toEqual("A");
expect(f2.toUpperCase()).toEqual("A");
expect(f3).toEqual("Focus Map");
expect(f4).toEqual("Previous Feature");
expect(f5).toEqual("Next Feature");
expect(f6).toEqual("Focus Controls");
expect(f7).toEqual("leaflet-popup-close-button");
});

test("[" + browserType + "]" + " Tab to next feature after tabbing out of popup", async () => {
await page.keyboard.press("Tab");

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);
Expand All @@ -182,19 +188,19 @@ jest.setTimeout(50000);
expect(f).toEqual("M-53 451L153 508L113 146L-53 191z");
});

test("[" + browserType + "]" + " Shift + Tab to previous feature while popup open", async () => {
test("[" + browserType + "]" + " Shift + Tab to current feature while popup open", async () => {
await page.keyboard.press("Enter");
await page.keyboard.press("Shift+Tab");

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.getAttribute("d"), rh)).jsonValue();

expect(f).toEqual("M330 83L553 83L553 339L330 339z");
expect(f).toEqual("M-53 451L153 508L113 146L-53 191z");
});

test("[" + browserType + "]" + " Previous feature button focuses previous feature", async () => {
await page.keyboard.press("Tab");
await page.keyboard.press("Enter");
await page.keyboard.press("Tab");
await page.keyboard.press("Tab");
Expand Down