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

Adding tabbing navigation and accessibility to map #270

Merged
merged 25 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
831e5fb
Allow features to be focused
ahmadayubi Jan 27, 2021
ba5078b
Adds crosshair, shows on keyboard movement
ahmadayubi Jan 28, 2021
1c75af6
Shows crosshair on tab
ahmadayubi Jan 28, 2021
8d82998
Only run on moveend if there are queryable layers
ahmadayubi Jan 28, 2021
9d9029e
Revert listener change
ahmadayubi Jan 29, 2021
97b811d
Adds tests for keyboard interaction
ahmadayubi Jan 29, 2021
5812169
Merge branch 'master' into featureFocus
prushforth Feb 1, 2021
f7f1497
Merge branch 'master' into featureFocus
ahmadayubi Feb 1, 2021
64476e6
Merge branch 'master' into featureFocus
ahmadayubi Feb 2, 2021
f5911b0
Remove close button from feature popup
ahmadayubi Feb 2, 2021
61a83d4
Add bypass navigation
ahmadayubi Feb 3, 2021
edc2cf0
Query popup fix
ahmadayubi Feb 3, 2021
a8d82fd
Merge branch 'master' into featureFocus
prushforth Feb 3, 2021
525d68e
Merge branch 'master' into featureFocus
prushforth Feb 3, 2021
2ed2749
Merge branch 'master' into featureFocus
prushforth Feb 3, 2021
3147842
Add feature count, move controls to bottom
ahmadayubi Feb 4, 2021
783d521
Merge branch 'featureFocus' of https://github.com/ahmadayubi/Web-Map-…
ahmadayubi Feb 4, 2021
8bc75ce
Test update to consider skip buttons
ahmadayubi Feb 4, 2021
69478aa
Test update
ahmadayubi Feb 4, 2021
7326fa1
Adds next and previous focus buttons
ahmadayubi Feb 4, 2021
19aaf5e
Add tests for keyboard interaction
ahmadayubi Feb 5, 2021
d227589
Rename variables and add comments
ahmadayubi Feb 5, 2021
8b3b1ef
Remove handlers on close
ahmadayubi Feb 5, 2021
0f69555
Merge branch 'master' into featureFocus
ahmadayubi Feb 10, 2021
a6c7f8d
Merge branch 'master' into featureFocus
prushforth Feb 10, 2021
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
2 changes: 1 addition & 1 deletion .github/workflows/sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ jobs:
destination_folder: 'dist'
user_email: aayub041@uottawa.ca
user_name: 'ahmadayubi'
commit_msg: 'Sync MapML Build'
commit_msg: '[AUTO] Sync MapML Build'
destination_branch: main
1 change: 1 addition & 0 deletions src/mapml-viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export class MapViewer extends HTMLElement {
this._attributionControl = this._map.attributionControl.setPrefix('<a href="https://www.w3.org/community/maps4html/" title="W3C Maps for HTML Community Group">Maps4HTML</a> | <a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>');

this.setControls(false,false,true);
this._crosshair = M.crosshair().addTo(this._map);

// Make the Leaflet container element programmatically identifiable
// (https://github.com/Leaflet/Leaflet/issues/7193).
Expand Down
38 changes: 38 additions & 0 deletions src/mapml.css
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,41 @@ summary {
.leaflet-container .leaflet-control-container {
visibility: unset!important;
}

.mapml-crosshair {
margin: -18px 0 0 -18px;
width: 36px;
height: 36px;
left: 50%;
top: 50%;
content: '';
display: block;
position: absolute;
z-index: 10000;
}

.mapml-popup-button {
padding: 0 4px 0 4px;
border: none;
text-align: center;
width: 18px;
height: 14px;
font: 16px/14px Tahoma, Verdana, sans-serif;
color: #c3c3c3;
text-decoration: none;
font-weight: bold;
background: transparent;
white-space: nowrap;
}

.mapml-focus-buttons {
white-space: nowrap;
display: inline;
}

.mapml-feature-count {
display:inline;
white-space: nowrap;
text-align: center;
padding: 2px;
}
13 changes: 8 additions & 5 deletions src/mapml/handlers/QueryHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,26 +151,29 @@ export var QueryHandler = L.Handler.extend({
});
f.addTo(map);

var c = document.createElement('iframe');
let div = L.DomUtil.create("div", "mapml-popup-content"),
c = L.DomUtil.create("iframe");
c.csp = "script-src 'none'";
c.style = "border: none";
c.srcdoc = mapmldoc.querySelector('feature properties').innerHTML;

div.appendChild(c);
// passing a latlng to the popup is necessary for when there is no
// geometry / null geometry
layer.bindPopup(c, popupOptions).openPopup(loc);
layer.bindPopup(div, popupOptions).openPopup(loc);
layer.on('popupclose', function() {
map.removeLayer(f);
});
});
}
function handleOtherResponse(response, layer, loc) {
return response.text().then(text => {
var c = document.createElement('iframe');
let div = L.DomUtil.create("div", "mapml-popup-content"),
c = L.DomUtil.create("iframe");
c.csp = "script-src 'none'";
c.style = "border: none";
c.srcdoc = text;
layer.bindPopup(c, popupOptions).openPopup(loc);
div.appendChild(c);
layer.bindPopup(div, popupOptions).openPopup(loc);
});
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/mapml/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { QueryHandler } from './handlers/QueryHandler';
import { ContextMenu } from './handlers/ContextMenu';
import { Util } from './utils/Util';
import { ReloadButton, reloadButton } from './control/ReloadButton';
import { Crosshair, crosshair } from "./layers/Crosshair";

/* global L, Node */
(function (window, document, undefined) {
Expand Down Expand Up @@ -627,4 +628,7 @@ M.mapMLStaticTileLayer = mapMLStaticTileLayer;
M.DebugOverlay = DebugOverlay;
M.debugOverlay = debugOverlay;

M.Crosshair = Crosshair;
M.crosshair = crosshair;

}(window, document));
95 changes: 95 additions & 0 deletions src/mapml/layers/Crosshair.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
export var Crosshair = L.Layer.extend({
onAdd: function (map) {

//SVG crosshair design from https://github.com/xguaita/Leaflet.MapCenterCoord/blob/master/src/icons/MapCenterCoordIcon1.svg?short_path=81a5c76
let svgInnerHTML = `<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 99.999998 99.999998"
xml:space="preserve">
<g><circle
r="3.9234731"
cy="50.21946"
cx="50.027821"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /><path
d="m 4.9734042,54.423642 31.7671398,0 c 2.322349,0 4.204185,-1.881836 4.204185,-4.204185 0,-2.322349 -1.881836,-4.204184 -4.204185,-4.204184 l -31.7671398,0 c -2.3223489,-2.82e-4 -4.20418433,1.881554 -4.20418433,4.204184 0,2.322631 1.88183543,4.204185 4.20418433,4.204185 z"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /><path
d="m 54.232003,5.1650429 c 0,-2.3223489 -1.881836,-4.20418433 -4.204184,-4.20418433 -2.322349,0 -4.204185,1.88183543 -4.204185,4.20418433 l 0,31.7671401 c 0,2.322349 1.881836,4.204184 4.204185,4.204184 2.322348,0 4.204184,-1.881835 4.204184,-4.204184 l 0,-31.7671401 z"
style="fill:#000000;stroke:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" /><path
d="m 99.287826,50.219457 c 0,-2.322349 -1.881835,-4.204184 -4.204184,-4.204184 l -31.76714,0 c -2.322349,0 -4.204184,1.881835 -4.204184,4.204184 0,2.322349 1.881835,4.204185 4.204184,4.204185 l 31.76714,0 c 2.320658,0 4.204184,-1.881836 4.204184,-4.204185 z"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /><path
d="m 45.823352,95.27359 c 0,2.322349 1.881836,4.204184 4.204185,4.204184 2.322349,0 4.204184,-1.881835 4.204184,-4.204184 l 0,-31.76714 c 0,-2.322349 -1.881835,-4.204185 -4.204184,-4.204185 -2.322349,0 -4.204185,1.881836 -4.204185,4.204185 l 0,31.76714 z"
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /></g></svg>
`;

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

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


this._addOrRemoveCrosshair();
},

_toggleEvents: function () {
if (this._hasQueryableLayer()) {
this._map.on("viewreset move moveend", this._addOrRemoveCrosshair, this);
} else {
this._map.off("viewreset move moveend", this._addOrRemoveCrosshair, this);
}
this._addOrRemoveCrosshair();
},

_addOrRemoveCrosshair: function (e) {
if (this._hasQueryableLayer()) {
this._container.style.visibility = null;
} else {
this._container.style.visibility = "hidden";
}
},

_hasQueryableLayer: function () {
let layers = this._map.options.mapEl.layers;
if (this._mapFocused) {
for (let layer of layers) {
if (layer.checked && layer._layer.queryable) {
return true;
}
}
}
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
} else {
this._mapFocused = false;
}
this._addOrRemoveCrosshair();
},

});


export var crosshair = function (options) {
return new Crosshair(options);
};
142 changes: 142 additions & 0 deletions src/mapml/layers/FeatureLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ export var MapMLFeatures = L.FeatureGroup.extend({
}
},

onAdd: function(map){
L.FeatureGroup.prototype.onAdd.call(this, map);
map.on("popupopen", this._attachSkipButtons, this);
this._updateTabIndex();
},

getEvents: function(){
if(this._staticFeature){
return {
Expand All @@ -47,6 +53,141 @@ export var MapMLFeatures = L.FeatureGroup.extend({
};
},

_updateTabIndex: function(){
for(let feature in this._features){
for(let path of this._features[feature]){
if(path._path){
if(path._path.getAttribute("d") !== "M0 0"){
path._path.setAttribute("tabindex", 0);
} else {
path._path.removeAttribute("tabindex");
}
if(path._path.childElementCount === 0) {
let title = document.createElement("title");
title.innerText = "Feature";
path._path.appendChild(title);
}
}
}
}
},

_attachSkipButtons: function(e){
if(!e.popup._source._path) return;
if(!e.popup._container.querySelector('div[class="mapml-focus-buttons"]')){
//add when popopen event happens instead
let div = L.DomUtil.create("div", "mapml-focus-buttons");

// creates |< button, focuses map
let mapFocusButton = L.DomUtil.create('a',"mapml-popup-button", div);
mapFocusButton.href = '#';
mapFocusButton.role = "button";
mapFocusButton.title = "Focus Map";
mapFocusButton.innerHTML = '|&#10094;';
L.DomEvent.disableClickPropagation(mapFocusButton);
L.DomEvent.on(mapFocusButton, 'click', L.DomEvent.stop);
L.DomEvent.on(mapFocusButton, 'click', this._skipBackward, this);

// creates < button, focuses previous feature, if none exists focuses the current feature
let previousButton = L.DomUtil.create('a', "mapml-popup-button", div);
previousButton.href = '#';
previousButton.role = "button";
previousButton.title = "Previous Feature";
previousButton.innerHTML = "&#10094;";
L.DomEvent.disableClickPropagation(previousButton);
L.DomEvent.on(previousButton, 'click', L.DomEvent.stop);
L.DomEvent.on(previousButton, 'click', this._previousFeature, e.popup);

// static feature counter that 1/1
let featureCount = L.DomUtil.create("p", "mapml-feature-count", div), currentFeature = 1;
featureCount.innerText = currentFeature+"/1";
//for(let feature of e.popup._source._path.parentNode.children){
// if(feature === e.popup._source._path)break;
// currentFeature++;
//}
//featureCount.innerText = currentFeature+"/"+e.popup._source._path.parentNode.childElementCount;

// creates > button, focuses next feature, if none exists focuses the current feature
let nextButton = L.DomUtil.create('a', "mapml-popup-button", div);
nextButton.href = '#';
nextButton.role = "button";
nextButton.title = "Next Feature";
nextButton.innerHTML = "&#10095;";
L.DomEvent.disableClickPropagation(nextButton);
L.DomEvent.on(nextButton, 'click', L.DomEvent.stop);
L.DomEvent.on(nextButton, 'click', this._nextFeature, e.popup);

// creates >| button, focuses map controls
let controlFocusButton = L.DomUtil.create('a',"mapml-popup-button", div);
controlFocusButton.href = '#';
controlFocusButton.role = "button";
controlFocusButton.title = "Focus Controls";
controlFocusButton.innerHTML = '&#10095;|';
L.DomEvent.disableClickPropagation(controlFocusButton);
L.DomEvent.on(controlFocusButton, 'click', L.DomEvent.stop);
L.DomEvent.on(controlFocusButton, 'click', this._skipForward, this);

let divider = L.DomUtil.create("hr");
divider.style.borderTop = "1px solid #bbb";

e.popup._content.appendChild(divider);
e.popup._content.appendChild(div);
}

// 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){
L.DomEvent.stop(focusEvent);
e.popup._source._path.focus();
} else if(focusEvent.originalEvent.shiftKey && +focusEvent.originalEvent.keyCode === 9){
e.target.closePopup(e.popup);
L.DomEvent.stop(focusEvent);
e.popup._source._path.focus();
}
}

function removeHandlers(removeEvent){
if (removeEvent.popup === e.popup){
e.target.off("keydown", focusFeature);
e.target.off("popupclose", removeHandlers);
}
}
// e.target = this._map
// Looks for keydown, more specifically tab and shift tab
e.target.on("keydown", focusFeature);

// if popup closes then the focusFeature handler can be removed
e.target.on("popupclose", removeHandlers);
},

_skipBackward: function(e){
this._map.closePopup();
this._map._container.focus();
},

_previousFeature: function(e){
this._map.closePopup();
if(this._source._path.previousSibling){
this._source._path.previousSibling.focus();
} else {
this._source._path.focus();
}
},

_nextFeature: function(e){
this._map.closePopup();
if(this._source._path.nextSibling){
this._source._path.nextSibling.focus();
} else {
this._source._path.focus();
}
},

_skipForward: function(e){
this._map.closePopup();
this._map._controlContainer.focus();
},

_handleMoveEnd : function(){
let mapZoom = this._map.getZoom();
if(mapZoom > this.zoomBounds.maxZoom || mapZoom < this.zoomBounds.minZoom){
Expand All @@ -62,6 +203,7 @@ export var MapMLFeatures = L.FeatureGroup.extend({
this._map.getPixelBounds(),
mapZoom,this._map.options.projection));
this._removeCSS();
this._updateTabIndex();
},

//sets default if any are missing, better to only replace ones that are missing
Expand Down
Loading