Add aria-label to map markers
Axe was reporting an accessibility error: ``` Found 1 accessibility violation: 1) aria-command-name: ARIA commands must have an accessible name (serious) https://dequeuniversity.com/rules/axe/4.11/aria-command-name?application=axeAPI The following 1 node violate this rule: Selector: .leaflet-marker-icon HTML: <div class="leaflet-marker-icon map-marker leaflet-zoom-animated leaflet-interactive" tabindex="0" role="button"> <div class="map-icon"></div> </div> Fix any of the following: - Element does not have text that is visible to screen readers - aria-label attribute does not exist or is empty - aria-labelledby attribute does not exist, references elements that do not exist or references elements that are empty - Element has no title attribute ``` Using the title of the proposal/investment as the text of the marker is definitely a good solution when there are several markers on the map. Not sure whether there's a better option when there's only one marker, though. Note that we aren't providing a proper aria-label for markers on the map we use in the form to create a proposal or an investment. Adding one isn't trivial given the current code, and keyboard users can't add a marker in the first place. We'll have to revisit this issue when we add keyboard support for this. We're also changing a test to make sure that titles with quotes in their names don't break the markup due to an invalid aria-label attribute.
This commit is contained in:
@@ -28,17 +28,19 @@
|
||||
markers = L.layerGroup();
|
||||
}
|
||||
marker = null;
|
||||
markerIcon = L.divIcon({
|
||||
className: "map-marker",
|
||||
iconSize: [30, 30],
|
||||
iconAnchor: [15, 40],
|
||||
html: '<div class="map-icon"></div>'
|
||||
});
|
||||
createMarker = function(latitude, longitude) {
|
||||
markerIcon = function(alt_text) {
|
||||
return L.divIcon({
|
||||
className: "map-marker",
|
||||
iconSize: [30, 30],
|
||||
iconAnchor: [15, 40],
|
||||
html: $('<div class="map-icon"></div>').attr("aria-label", alt_text)[0].outerHTML
|
||||
});
|
||||
};
|
||||
createMarker = function(latitude, longitude, text) {
|
||||
var newMarker, markerLatLng;
|
||||
markerLatLng = new L.LatLng(latitude, longitude);
|
||||
newMarker = L.marker(markerLatLng, {
|
||||
icon: markerIcon,
|
||||
icon: markerIcon(text),
|
||||
draggable: editable
|
||||
});
|
||||
if (editable) {
|
||||
@@ -71,7 +73,7 @@
|
||||
|
||||
markerData = App.Map.markerData(element);
|
||||
if (markerData.lat && markerData.long && !investmentsMarkers) {
|
||||
marker = createMarker(markerData.lat, markerData.long);
|
||||
marker = createMarker(markerData.lat, markerData.long, markerData.title);
|
||||
}
|
||||
if (editable) {
|
||||
$(removeMarkerSelector).on("click", removeMarker);
|
||||
@@ -115,7 +117,8 @@
|
||||
|
||||
dataCoordinates = {
|
||||
lat: $(element).data("marker-latitude"),
|
||||
long: $(element).data("marker-longitude")
|
||||
long: $(element).data("marker-longitude"),
|
||||
title: $(element).data("marker-title")
|
||||
};
|
||||
formCoordinates = {
|
||||
lat: inputs.lat.val(),
|
||||
@@ -133,6 +136,7 @@
|
||||
return {
|
||||
lat: latitude,
|
||||
long: longitude,
|
||||
title: dataCoordinates.title,
|
||||
zoom: formCoordinates.zoom
|
||||
};
|
||||
},
|
||||
@@ -188,7 +192,7 @@
|
||||
var marker;
|
||||
|
||||
if (App.Map.validCoordinates(coordinates)) {
|
||||
marker = createMarker(coordinates.lat, coordinates.long);
|
||||
marker = createMarker(coordinates.lat, coordinates.long, coordinates.title);
|
||||
marker.options.id = coordinates.investment_id;
|
||||
marker.bindPopup(App.Map.getPopupContent(coordinates));
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ class Shared::MapLocationComponent < ApplicationComponent
|
||||
marker_investments_coordinates: investments_coordinates,
|
||||
marker_latitude: map_location.latitude.presence,
|
||||
marker_longitude: map_location.longitude.presence,
|
||||
marker_title: map_location.title.presence,
|
||||
marker_clustering: feature?("map.feature.marker_clustering"),
|
||||
geozones: geozones_data
|
||||
}.merge(input_selectors)
|
||||
|
||||
@@ -8,6 +8,10 @@ class MapLocation < ApplicationRecord
|
||||
latitude.present? && longitude.present? && zoom.present?
|
||||
end
|
||||
|
||||
def title
|
||||
(proposal || investment)&.title
|
||||
end
|
||||
|
||||
def json_data
|
||||
{
|
||||
investment_id: investment_id,
|
||||
|
||||
@@ -36,7 +36,8 @@ shared_examples "mappable" do |mappable_factory_name, mappable_association_name,
|
||||
find("#new_map_location").click
|
||||
|
||||
within ".map-location" do
|
||||
expect(page).to have_css(".map-icon")
|
||||
expect(page).to have_css ".map-icon"
|
||||
expect(page).not_to have_css ".map-icon[aria-label]"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -274,14 +275,15 @@ shared_examples "mappable" do |mappable_factory_name, mappable_association_name,
|
||||
set_arguments(arguments, mappable, mappable_path_arguments)
|
||||
end
|
||||
|
||||
scenario "Should display map and marker on #{mappable_factory_name} show page" do
|
||||
scenario "Should display marker on #{mappable_factory_name} show page with aria label" do
|
||||
arguments[:id] = mappable.id
|
||||
mappable.update!(title: "Malformed quote\" and >")
|
||||
|
||||
do_login_for user, management: management if management
|
||||
visit send(mappable_show_path, arguments)
|
||||
|
||||
within ".map-location" do
|
||||
expect(page).to have_css(".map-icon")
|
||||
expect(page).to have_css ".map-icon[aria-label='#{mappable.title}']"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -193,6 +193,8 @@ describe "Ballots" do
|
||||
expect(page).to have_content "OpenStreetMap"
|
||||
expect(page).to have_content "New Block"
|
||||
expect(page).to have_css ".map-icon", visible: :all, count: 2
|
||||
expect(page).to have_css ".map-icon[aria-label='More bridges']", visible: :all
|
||||
expect(page).to have_css ".map-icon[aria-label='Less bridges']", visible: :all
|
||||
end
|
||||
|
||||
add_to_ballot("More bridges")
|
||||
|
||||
Reference in New Issue
Block a user