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 10 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
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
12 changes: 12 additions & 0 deletions src/mapml.css
Original file line number Diff line number Diff line change
Expand Up @@ -374,3 +374,15 @@ 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;
}
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 @@ -625,4 +626,7 @@ M.mapMLStaticTileLayer = mapMLStaticTileLayer;
M.DebugOverlay = DebugOverlay;
M.debugOverlay = debugOverlay;

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

}(window, document));
88 changes: 88 additions & 0 deletions src/mapml/layers/Crosshair.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
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._isMapFocused, 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;
},

_isMapFocused: function (e) {
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;
} else if (e.type === "keyup" && e.target.classList.contains("leaflet-container") && +e.keyCode === 9) {
this._mapFocused = true;
} else {
this._mapFocused = false;
}
this._addOrRemoveCrosshair();
},

});


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

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

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

_updateTabIndex: function(){
for(let feature in this._features){
for(let path of this._features[feature]){
if(path._path && path._path.getAttribute("d") !== "M0 0") path._path.setAttribute("tabindex", 0);
}
}
},

_handleMoveEnd : function(){
let mapZoom = this._map.getZoom();
if(mapZoom > this.zoomBounds.maxZoom || mapZoom < this.zoomBounds.minZoom){
Expand All @@ -62,6 +75,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
4 changes: 2 additions & 2 deletions src/mapml/layers/MapLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export var MapMLLayer = L.Layer.extend({
if (properties) {
var c = document.createElement('div');
c.insertAdjacentHTML('afterbegin', properties.innerHTML);
geometry.bindPopup(c, {autoPan:false});
geometry.bindPopup(c, {autoPan:false, closeButton: false});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are close buttons not useful in feature's popups?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @shepazu mentioned they might be problematic, or it might have been me misinterpreting something...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was suggested that it be removed, should it be added back?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having a close button is fine, with the following caveats:

  • the popup should not be a modal (that is, it shouldn't be a focus trap)
  • if possible, we shouldn't force screen reader users to "close" each feature popup to move on to another feature

This seems like somewhat competing experiences between voice-control and screen-reader users, in exposing the right amount of detail with the fewest possible number of forced interactions. My preference would be to have the popup not exposed to screen readers on focus, but only read the accessible name (the popup "title"), with notification of additional information (e.g. aria-expanded) to access the other popup details only on demand (at which point they might encounter the close button, but that's fine, because they chose to expand it). For voice-control users, I think having the popup on focus should be good, with scrolling for long details, and a close button.

Does this seem right to you, @Malvoz?

Copy link
Member

@Malvoz Malvoz Feb 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having a close button is fine, with the following caveats:

  • the popup should not be a modal (that is, it shouldn't be a focus trap)

The popups are non-modal, so focus traps are (mostly*) not an issue.

*when the popup for the last feature in the sequence is opened tabbing is currently trapped to some extent (shift + tab can be used to move back to the previous feature):

Keyboard.Interaction.Test.-.Google.Chrome.2021-02-08.11-06-.mp4

if possible, we shouldn't force screen reader users to "close" each feature popup to move on to another feature

Because focus (generally) isn't trapped inside popups users don't have to explicitly close popups before moving on to other features/focusable elements.

This seems like somewhat competing experiences between voice-control and screen-reader users, in exposing the right amount of detail with the fewest possible number of forced interactions.

I'm not super familiar with voice-control so I'm not sure about how there are competing experiences here. I do know that some people use voice-control to move their cursor around to interact with elements, in that case I think a close button could be appreciated if they just wanted to close a popup without opening a new one.

My preference would be to have the popup not exposed to screen readers on focus, but only read the accessible name (the popup "title"), with notification of additional information (e.g. aria-expanded) to access the other popup details only on demand (at which point they might encounter the close button, but that's fine, because they chose to expand it).

Yes I think we agreed that popups should not be opened (and thus exposed to ATs) when a feature recieves focus, and that each feature itself should be labelled for discernibility. (discussion from #270 (comment))

I agree that features that are interactive and have popups should expose the state of the popup using aria-expanded.

}
}
});
Expand Down Expand Up @@ -123,7 +123,7 @@ export var MapMLLayer = L.Layer.extend({
if (properties) {
var c = document.createElement('div');
c.insertAdjacentHTML('afterbegin', properties.innerHTML);
geometry.bindPopup(c, {autoPan:false});
geometry.bindPopup(c, {autoPan:false, closeButton: false});
}
}
}).addTo(map);
Expand Down
11 changes: 10 additions & 1 deletion src/mapml/layers/TemplatedFeaturesLayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
// need to parse as HTML to preserve semantics and styles
var c = document.createElement('div');
c.insertAdjacentHTML('afterbegin', properties.innerHTML);
geometry.bindPopup(c, {autoPan:false});
geometry.bindPopup(c, {autoPan:false, closeButton: false});
}
});
}
Expand Down Expand Up @@ -87,6 +87,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
parser = new DOMParser(),
features = this._features,
map = this._map,
context = this,
MAX_PAGES = 10,
_pullFeatureFeed = function (url, limit) {
return (fetch (url,{redirect: 'follow',headers: headers})
Expand All @@ -111,6 +112,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
_pullFeatureFeed(this._getfeaturesUrl(), MAX_PAGES)
.then(function() {
map.addLayer(features);
M.TemplatedFeaturesLayer.prototype._updateTabIndex(context);
})
.catch(function (error) { console.log(error);});
},
Expand All @@ -119,6 +121,13 @@ export var TemplatedFeaturesLayer = L.Layer.extend({
this._updateZIndex();
return this;
},
_updateTabIndex: function(context){
let c = context || this;
for(let layerNum in c._features._layers){
let layer = c._features._layers[layerNum];
if(layer._path && layer._path.getAttribute("d") !== "M0 0") layer._path.setAttribute("tabindex", 0);
}
},
_updateZIndex: function () {
if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
this._container.style.zIndex = this.options.zIndex;
Expand Down
1 change: 1 addition & 0 deletions src/web-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export class WebMap extends HTMLMapElement {
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);
if (this.hasAttribute('name')) {
var name = this.getAttribute('name');
if (name) {
Expand Down
85 changes: 85 additions & 0 deletions test/e2e/core/keyboardInteraction.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<html>

<head>
<title>Keyboard Interaction Test</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="module" src="mapml-viewer.js"></script>
</head>

<body>
<mapml-viewer style="width: 500px;height: 500px;" is="web-map" projection="CBMTILE" zoom="2" lat="45.5052040"
lon="-75.2202344" controls>
<layer- label="Fire" checked>
<extent units="CBMTILE">
<input name="z" type="zoom" value="18" min="0" max="18" />
<input name="txmin" type="location" units="tilematrix" position="top-left" axis="easting" min="-2.003750834E7"
max="2.003750834E7" />
<input name="tymin" type="location" units="tilematrix" position="bottom-left" axis="northing"
min="-2.003750834E7" max="2.003750834E7" />
<input name="txmax" type="location" units="tilematrix" position="top-right" axis="easting" min="-2.003750834E7"
max="2.003750834E7" />
<input name="tymax" type="location" units="tilematrix" position="top-left" axis="northing" min="-2.003750834E7"
max="2.003750834E7" />
<link rel="tile"
tref="https://cwfis.cfs.nrcan.gc.ca/geoserver/public/wms?service=WMS&version=1.3.0&request=GetMap&layers=public:fdr_current&styles=&bbox={txmin},{tymin},{txmax},{tymax}&width=256&height=256&crs=EPSG:3978&FORMAT=image/png&TRANSPARENT=TRUE&m4h=t" />
<input name="i" type="location" units="map" axis="i" />
<input name="j" type="location" units="map" axis="j" />
<input name="xmin" type="location" units="pcrs" position="top-left" axis="easting" min="-2.003750834E7"
max="2.003750834E7" />
<input name="ymin" type="location" units="pcrs" position="bottom-left" axis="northing" min="-2.003750834E7"
max="2.003750834E7" />
<input name="xmax" type="location" units="pcrs" position="top-right" axis="easting" min="-2.003750834E7"
max="2.003750834E7" />
<input name="ymax" type="location" units="pcrs" position="top-left" axis="northing" min="-2.003750834E7"
max="2.003750834E7" />
<input name="w" type="width" />
<input name="h" type="height" />
<link rel="query"
tref="https://cwfis.cfs.nrcan.gc.ca/geoserver/public/wms?i={i}&j={j}&service=WMS&version=1.3.0&request=GetFeatureInfo&layers=public:fdr_current&QUERY_LAYERS=current:fdr_current&styles=&bbox={xmin},{ymin},{xmax},{ymax}&width={w}&height={h}&srs=EPSG:3978&INFO_FORMAT=text/html&m4h=t" />
</extent>
</layer->

<layer- label="Arizona" checked>
<meta name="projection" content="CBMTILE" />
<meta name="zoom" content="min=1,max=5,value=0" />
<meta name="cs" content="gcrs" />
<meta name="extent" content="zoom=0,top-left-column=0,top-left-row=0,bottom-right-column=5,bottom-right-row=5" />
<feature zoom="2" class="refDiff">
<properties>
<h1>Test</h1>
</properties>
<geometry cs="tilematrix">
<polygon>
<coordinates>11 11 12 11 12 12 11 12</coordinates>
</polygon>
</geometry>
</feature>

<feature zoom="2" class="refDiff">
<properties>
<h1>Test</h1>
</properties>
<geometry cs="pcrs">
<polygon>
<coordinates>257421 -3567196 -271745 1221771 -3896544 242811 -3183549 -2613313</coordinates>
</polygon>
</geometry>
</feature>

<feature zoom="2" class="refDiff">
<properties>
<h1>Test</h1>
</properties>
<geometry cs="tcrs">
<polygon>
<coordinates>2771 3106 2946 3113 2954 3210 2815 3192</coordinates>
</polygon>
</geometry>
</feature>
</layer->
<layer- id="vector" label="vector states" src="data/us_pop_density.mapml"></layer->
</mapml-viewer>
</body>

</html>
Loading