From 7503bb9340ba3123868017414d5550e4ec3ecd43 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Mon, 13 Dec 2021 10:12:57 -0500 Subject: [PATCH 01/31] Add feature indexing --- src/mapml-viewer.js | 2 +- src/mapml.css | 54 +++++++++++++++- src/mapml/index.js | 4 ++ src/mapml/layers/FeatureIndex.js | 106 +++++++++++++++++++++++++++++++ src/web-map.js | 2 + 5 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 src/mapml/layers/FeatureIndex.js diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index b3151cf88..960893ffa 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -219,7 +219,7 @@ export class MapViewer extends HTMLElement { this.setControls(false,false,true); this._crosshair = M.crosshair().addTo(this._map); - + this._featureIndexBox = M.featureIndex().addTo(this._map); // https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/274 this.setAttribute('role', 'application'); // Make the Leaflet container element programmatically identifiable diff --git a/src/mapml.css b/src/mapml.css index a2fe41969..c8d9a3fff 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -649,7 +649,7 @@ button.mapml-button:disabled, box-sizing: border-box; } -.mapml-layer-item, +.mapml-layer-item, .mapml-layer-grouped-extents, .mapml-layer-extent { background-color: #fff; @@ -751,7 +751,7 @@ label.mapml-layer-item-toggle { /* * Feature styles. */ - + .mapml-vector-container svg :is( [role="link"]:focus, [role="link"]:hover, @@ -803,3 +803,53 @@ label.mapml-layer-item-toggle { right: 0; } + + +/** +* Feature Index + */ +.mapml-feature-index-box { + margin: -36px 0 0 -36px; + width: 72px; + height: 72px; + left: 50%; + top: 50%; + content: ''; + display: block; + position: absolute; + z-index: 10000; +} + +.mapml-feature-index { + contain: content; + max-height: 100%; + max-width: 100%; + border-radius: 4px; + padding: 5px 10px; + background-color: #fff; + cursor: default; + z-index: 1000; + position: absolute; + display: block; + top: auto; + right: 5px; + bottom: 5px; + left: 5px; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; + font: inherit; +} + +.mapml-feature-index-content { + font-family: monospace; +} + +.mapml-feature-index-header { + font-weight: bold; + text-transform: uppercase; + display: inline-block; + text-align: left; + text-align: inline-start; + line-height: 2; +} \ No newline at end of file diff --git a/src/mapml/index.js b/src/mapml/index.js index 6d587d814..ec401e131 100644 --- a/src/mapml/index.js +++ b/src/mapml/index.js @@ -61,6 +61,7 @@ import {AnnounceMovement} from "./handlers/AnnounceMovement"; import { FeatureIndex } from "./handlers/FeatureIndex"; import { Options } from "./options"; import "./keyboard"; +import {featureIndex, FeatureIndex} from "./layers/FeatureIndex"; /* global L, Node */ (function (window, document, undefined) { @@ -655,6 +656,9 @@ M.debugOverlay = debugOverlay; M.Crosshair = Crosshair; M.crosshair = crosshair; +M.FeatureIndex = FeatureIndex; +M.featureIndex = featureIndex; + M.Feature = Feature; M.feature = feature; diff --git a/src/mapml/layers/FeatureIndex.js b/src/mapml/layers/FeatureIndex.js new file mode 100644 index 000000000..e5a3768a7 --- /dev/null +++ b/src/mapml/layers/FeatureIndex.js @@ -0,0 +1,106 @@ +export var FeatureIndex = L.Layer.extend({ + onAdd: function (map) { + let svgInnerHTML = ` + + + + `; + + this._container = L.DomUtil.create("div", "mapml-feature-index-box", map._container); + this._container.innerHTML = svgInnerHTML; + + this._table = L.DomUtil.create("table", "mapml-feature-index", map._container); + this._title = L.DomUtil.create("caption", "mapml-feature-index-header", this._table); + this._title.innerHTML = "Feature Index"; + this._body = L.DomUtil.create("tbody", "mapml-feature-index-content", this._table); + + map.on('moveend', this._checkOverlap, this); + }, + + _checkOverlap: function () { + let bounds = this._map.getPixelBounds(); + let center = bounds.getCenter(); + let wRatio = Math.abs(bounds.min.x - bounds.max.x) / (this._map.options.mapEl.width); + let hRatio = Math.abs(bounds.min.y - bounds.max.y) / (this._map.options.mapEl.height); + + let w = wRatio * 36; + let h = hRatio * 36; + let minPoint = L.point(center.x - w, center.y + h); + let maxPoint = L.point(center.x + w, center.y - h); + let b = L.bounds(minPoint, maxPoint); + let featureIndexBounds = M.pixelToPCRSBounds(b,this._map.getZoom(),this._map.options.projection); + + let layers = this._map._layers; + let index = 1; + let keys = Object.keys(layers); + let body = this._body; + + body.innerHTML = ""; + + keys.forEach(i => { + if(layers[i].featureAttributes && featureIndexBounds.overlaps(layers[i]._bounds)){ + let label = layers[i].group.getAttribute("aria-label"); + + if(index === 9){ + body.appendChild(this._updateCell("More results", 9)); + body.querySelector("tr:nth-child(9) > td").addEventListener('focus', this._showMoreResults(body)); + index += 1; + } + if(index > 9){ + this._moreResults(label, index, body); + } else { + body.appendChild(this._updateCell(label, index)); + } + index += 1; + + } + }); + }, + + _updateCell: function (label, index) { + let row = document.createElement("tr"); + let cell = document.createElement("td"); + + row.setAttribute("row", index); + cell.setAttribute("tabindex", index); + cell.setAttribute("aria-label", label); + cell.innerHTML = index + " " + label; + row.appendChild(cell); + return row; + }, + + _moreResults :function (label, index, body) { + let multiplier = Math.floor((index - 1) / 9); + let row = body.querySelector(`[row='${index - (9 * multiplier)}']`); + let cell = document.createElement("td"); + + cell.className = "more-results"; + cell.style.display = "none"; + cell.setAttribute("tabindex", index); + cell.setAttribute("aria-label", label); + cell.innerHTML = index + " " + label; + row.appendChild(cell); + }, + + _showMoreResults: function (body) { + return function () { + let hiddenCells = body.getElementsByClassName("more-results"); + for (let i = 0; i < hiddenCells.length; i++){ + hiddenCells[i].style.display = ""; + } + }; + }, + +}); + +export var featureIndex = function (options) { + return new FeatureIndex(options); +}; \ No newline at end of file diff --git a/src/web-map.js b/src/web-map.js index 1c142a325..ba15aabfc 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -234,6 +234,8 @@ export class WebMap extends HTMLMapElement { this.setControls(false,false,true); this._crosshair = M.crosshair().addTo(this._map); + this._featureIndexBox = M.featureIndexBox().addTo(this._map); + if (this.hasAttribute('name')) { var name = this.getAttribute('name'); if (name) { From 28b3a33fefe15f7ef85f41084264100272becfe3 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Mon, 13 Dec 2021 22:53:15 -0500 Subject: [PATCH 02/31] Toggle feature index on or off --- src/mapml/layers/FeatureIndex.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/mapml/layers/FeatureIndex.js b/src/mapml/layers/FeatureIndex.js index e5a3768a7..41c382493 100644 --- a/src/mapml/layers/FeatureIndex.js +++ b/src/mapml/layers/FeatureIndex.js @@ -21,8 +21,9 @@ export var FeatureIndex = L.Layer.extend({ this._title = L.DomUtil.create("caption", "mapml-feature-index-header", this._table); this._title.innerHTML = "Feature Index"; this._body = L.DomUtil.create("tbody", "mapml-feature-index-content", this._table); - + map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this); map.on('moveend', this._checkOverlap, this); + this._addOrRemoveFeatureIndex(); }, _checkOverlap: function () { @@ -99,6 +100,27 @@ export var FeatureIndex = L.Layer.extend({ }; }, + _toggleEvents: function (){ + this._map.on("viewreset move moveend focus blur", this._addOrRemoveFeatureIndex, this); + + }, + + _addOrRemoveFeatureIndex: function (e) { + if (e && e.type === "focus"){ + this._container.querySelector('rect').style.display = "inline"; + this._table.style.display = "block"; + } else if (e && e.type === "blur") { + this._container.querySelector('rect').style.display = "none"; + this._table.style.display = "none"; + } else if (this._map.isFocused) { + this._container.querySelector('rect').style.display = "inline"; + this._table.style.display = "block"; + } else { + this._container.querySelector('rect').style.display = "none"; + this._table.style.display = "none"; + } + }, + }); export var featureIndex = function (options) { From 428c22e0b68507b67e7356fb33c9277f9e43c7ae Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Tue, 14 Dec 2021 10:14:47 -0500 Subject: [PATCH 03/31] Toggle feature index on or off --- src/mapml/layers/FeatureIndex.js | 33 +++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/mapml/layers/FeatureIndex.js b/src/mapml/layers/FeatureIndex.js index 41c382493..d58596d1d 100644 --- a/src/mapml/layers/FeatureIndex.js +++ b/src/mapml/layers/FeatureIndex.js @@ -106,19 +106,26 @@ export var FeatureIndex = L.Layer.extend({ }, _addOrRemoveFeatureIndex: function (e) { - if (e && e.type === "focus"){ - this._container.querySelector('rect').style.display = "inline"; - this._table.style.display = "block"; - } else if (e && e.type === "blur") { - this._container.querySelector('rect').style.display = "none"; - this._table.style.display = "none"; - } else if (this._map.isFocused) { - this._container.querySelector('rect').style.display = "inline"; - this._table.style.display = "block"; - } else { - this._container.querySelector('rect').style.display = "none"; - this._table.style.display = "none"; - } + let obj = this; + setTimeout(function() { + if (obj._table.contains(obj._map.options.mapEl.shadowRoot.activeElement)) { + return; + } + if (e && e.type === "focus") { + obj._container.querySelector('rect').style.display = "inline"; + obj._table.style.display = "block"; + } else if (e && e.type === "blur") { + obj._container.querySelector('rect').style.display = "none"; + obj._table.style.display = "none"; + } else if (obj._map.isFocused) { + obj._container.querySelector('rect').style.display = "inline"; + obj._table.style.display = "block"; + } else { + obj._container.querySelector('rect').style.display = "none"; + obj._table.style.display = "none"; + } + }, 0); + }, }); From 364442c475eb9683ad900af69ec155f8618e8497 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Wed, 15 Dec 2021 10:29:11 -0500 Subject: [PATCH 04/31] Check overlap on focus --- src/mapml/layers/FeatureIndex.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mapml/layers/FeatureIndex.js b/src/mapml/layers/FeatureIndex.js index d58596d1d..c5800325f 100644 --- a/src/mapml/layers/FeatureIndex.js +++ b/src/mapml/layers/FeatureIndex.js @@ -22,7 +22,7 @@ export var FeatureIndex = L.Layer.extend({ this._title.innerHTML = "Feature Index"; this._body = L.DomUtil.create("tbody", "mapml-feature-index-content", this._table); map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this); - map.on('moveend', this._checkOverlap, this); + map.on('moveend focus', this._checkOverlap, this); this._addOrRemoveFeatureIndex(); }, From 7b73f0b92a8264ca43b64619823b36bb5dae6d2c Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Wed, 15 Dec 2021 10:43:13 -0500 Subject: [PATCH 05/31] Fix function name --- src/mapml-viewer.js | 2 +- src/web-map.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 960893ffa..b9c59b066 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -219,7 +219,7 @@ export class MapViewer extends HTMLElement { this.setControls(false,false,true); this._crosshair = M.crosshair().addTo(this._map); - this._featureIndexBox = M.featureIndex().addTo(this._map); + this._featureIndex = M.featureIndex().addTo(this._map); // https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/274 this.setAttribute('role', 'application'); // Make the Leaflet container element programmatically identifiable diff --git a/src/web-map.js b/src/web-map.js index ba15aabfc..7cb67b14b 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -234,7 +234,7 @@ export class WebMap extends HTMLMapElement { this.setControls(false,false,true); this._crosshair = M.crosshair().addTo(this._map); - this._featureIndexBox = M.featureIndexBox().addTo(this._map); + this._featureIndex = M.featureIndex().addTo(this._map); if (this.hasAttribute('name')) { var name = this.getAttribute('name'); From 2772b200e25af990a7617ad0109eebb58214c203 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Wed, 15 Dec 2021 15:57:52 -0500 Subject: [PATCH 06/31] Replace table with output element --- src/mapml.css | 4 ++ src/mapml/layers/FeatureIndex.js | 76 ++++++++++++-------------------- 2 files changed, 33 insertions(+), 47 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index c8d9a3fff..a5c6b9495 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -842,6 +842,10 @@ label.mapml-layer-item-toggle { } .mapml-feature-index-content { + display: inline-block; +} +.mapml-feature-index-content > span{ + display: block; font-family: monospace; } diff --git a/src/mapml/layers/FeatureIndex.js b/src/mapml/layers/FeatureIndex.js index c5800325f..5c6478e3f 100644 --- a/src/mapml/layers/FeatureIndex.js +++ b/src/mapml/layers/FeatureIndex.js @@ -17,12 +17,14 @@ export var FeatureIndex = L.Layer.extend({ this._container = L.DomUtil.create("div", "mapml-feature-index-box", map._container); this._container.innerHTML = svgInnerHTML; - this._table = L.DomUtil.create("table", "mapml-feature-index", map._container); - this._title = L.DomUtil.create("caption", "mapml-feature-index-header", this._table); - this._title.innerHTML = "Feature Index"; - this._body = L.DomUtil.create("tbody", "mapml-feature-index-content", this._table); + this._output = L.DomUtil.create("output", "mapml-feature-index", map._container); + this._body = L.DomUtil.create("span", "mapml-feature-index-content", this._output); + this._moreContent = L.DomUtil.create("span", "mapml-feature-index-content more-content", this._output); + this._moreContent.style.display = "none"; + map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this); map.on('moveend focus', this._checkOverlap, this); + map.on("keydown", this._toggleMoreContent, this); this._addOrRemoveFeatureIndex(); }, @@ -46,58 +48,41 @@ export var FeatureIndex = L.Layer.extend({ body.innerHTML = ""; + let moreContent = this._moreContent; + moreContent.innerHTML = ""; + keys.forEach(i => { if(layers[i].featureAttributes && featureIndexBounds.overlaps(layers[i]._bounds)){ let label = layers[i].group.getAttribute("aria-label"); - if(index === 9){ - body.appendChild(this._updateCell("More results", 9)); - body.querySelector("tr:nth-child(9) > td").addEventListener('focus', this._showMoreResults(body)); + body.appendChild(this._updateOutput("More results", 9)); index += 1; } + if(index > 9){ - this._moreResults(label, index, body); + moreContent.appendChild(this._updateOutput(label, index)); } else { - body.appendChild(this._updateCell(label, index)); + body.appendChild(this._updateOutput(label, index)); } index += 1; } }); }, - - _updateCell: function (label, index) { - let row = document.createElement("tr"); - let cell = document.createElement("td"); - - row.setAttribute("row", index); - cell.setAttribute("tabindex", index); - cell.setAttribute("aria-label", label); - cell.innerHTML = index + " " + label; - row.appendChild(cell); - return row; + _updateOutput: function (label, index) { + let span = document.createElement("span"); + span.innerHTML = `${index}` + " " + label; + return span; }, - - _moreResults :function (label, index, body) { - let multiplier = Math.floor((index - 1) / 9); - let row = body.querySelector(`[row='${index - (9 * multiplier)}']`); - let cell = document.createElement("td"); - - cell.className = "more-results"; - cell.style.display = "none"; - cell.setAttribute("tabindex", index); - cell.setAttribute("aria-label", label); - cell.innerHTML = index + " " + label; - row.appendChild(cell); - }, - - _showMoreResults: function (body) { - return function () { - let hiddenCells = body.getElementsByClassName("more-results"); - for (let i = 0; i < hiddenCells.length; i++){ - hiddenCells[i].style.display = ""; + _toggleMoreContent: function (e){ + let display = this._moreContent.style.display; + if(e.originalEvent.keyCode === 57){ + if(display === "none"){ + this._moreContent.style.display = "inline-block"; + } else { + this._moreContent.style.display = "none"; } - }; + } }, _toggleEvents: function (){ @@ -108,21 +93,18 @@ export var FeatureIndex = L.Layer.extend({ _addOrRemoveFeatureIndex: function (e) { let obj = this; setTimeout(function() { - if (obj._table.contains(obj._map.options.mapEl.shadowRoot.activeElement)) { - return; - } if (e && e.type === "focus") { obj._container.querySelector('rect').style.display = "inline"; - obj._table.style.display = "block"; + obj._output.style.display = "block"; } else if (e && e.type === "blur") { obj._container.querySelector('rect').style.display = "none"; - obj._table.style.display = "none"; + obj._output.style.display = "none"; } else if (obj._map.isFocused) { obj._container.querySelector('rect').style.display = "inline"; - obj._table.style.display = "block"; + obj._output.style.display = "block"; } else { obj._container.querySelector('rect').style.display = "none"; - obj._table.style.display = "none"; + obj._output.style.display = "none"; } }, 0); From 54c9e6a329a893f22a42079ef77d37265796d6e9 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Thu, 16 Dec 2021 10:13:54 -0500 Subject: [PATCH 07/31] Add CSS for 'more results' --- src/mapml.css | 10 +++++++++- src/mapml/layers/FeatureIndex.js | 18 +++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index a5c6b9495..4e2994d04 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -825,7 +825,6 @@ label.mapml-layer-item-toggle { max-height: 100%; max-width: 100%; border-radius: 4px; - padding: 5px 10px; background-color: #fff; cursor: default; z-index: 1000; @@ -841,9 +840,18 @@ label.mapml-layer-item-toggle { font: inherit; } +.mapml-feature-index-more-content { + display: inline-block; +} +.mapml-feature-index-more-content > span{ + padding: 5px 10px 5px 0; + display: inline-block; +} .mapml-feature-index-content { + padding: 5px 10px; display: inline-block; } +.mapml-feature-index-more-content > span > span, .mapml-feature-index-content > span{ display: block; font-family: monospace; diff --git a/src/mapml/layers/FeatureIndex.js b/src/mapml/layers/FeatureIndex.js index 5c6478e3f..0be634e01 100644 --- a/src/mapml/layers/FeatureIndex.js +++ b/src/mapml/layers/FeatureIndex.js @@ -19,7 +19,7 @@ export var FeatureIndex = L.Layer.extend({ this._output = L.DomUtil.create("output", "mapml-feature-index", map._container); this._body = L.DomUtil.create("span", "mapml-feature-index-content", this._output); - this._moreContent = L.DomUtil.create("span", "mapml-feature-index-content more-content", this._output); + this._moreContent = L.DomUtil.create("span", "mapml-feature-index-more-content", this._output); this._moreContent.style.display = "none"; map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this); @@ -54,13 +54,21 @@ export var FeatureIndex = L.Layer.extend({ keys.forEach(i => { if(layers[i].featureAttributes && featureIndexBounds.overlaps(layers[i]._bounds)){ let label = layers[i].group.getAttribute("aria-label"); - if(index === 9){ - body.appendChild(this._updateOutput("More results", 9)); - index += 1; + + if(index%9 === 0){ + let span = document.createElement("span"); + span.setAttribute("id", index/9); + moreContent.appendChild(span); + if(index === 9){ + body.appendChild(this._updateOutput("More results", 9)); + index += 1; + } } if(index > 9){ - moreContent.appendChild(this._updateOutput(label, index)); + let value = Math.floor((index - 1)/9); + let span = moreContent.querySelector(`[id=${CSS.escape(value)}]`); + span.appendChild(this._updateOutput(label, index)); } else { body.appendChild(this._updateOutput(label, index)); } From 6e44a18a35f0588182d6960387d02b9836f4e258 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Fri, 17 Dec 2021 00:09:45 -0500 Subject: [PATCH 08/31] Limit output to having 9 items at a time --- src/mapml.css | 22 +-------- src/mapml/layers/FeatureIndex.js | 82 ++++++++++++++++++++------------ 2 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index 4e2994d04..7943ac829 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -834,34 +834,14 @@ label.mapml-layer-item-toggle { right: 5px; bottom: 5px; left: 5px; + padding: 5px 10px; width: -webkit-fit-content; width: -moz-fit-content; width: fit-content; font: inherit; } - -.mapml-feature-index-more-content { - display: inline-block; -} -.mapml-feature-index-more-content > span{ - padding: 5px 10px 5px 0; - display: inline-block; -} -.mapml-feature-index-content { - padding: 5px 10px; - display: inline-block; -} -.mapml-feature-index-more-content > span > span, .mapml-feature-index-content > span{ display: block; font-family: monospace; } -.mapml-feature-index-header { - font-weight: bold; - text-transform: uppercase; - display: inline-block; - text-align: left; - text-align: inline-start; - line-height: 2; -} \ No newline at end of file diff --git a/src/mapml/layers/FeatureIndex.js b/src/mapml/layers/FeatureIndex.js index 0be634e01..5bb4ff8f8 100644 --- a/src/mapml/layers/FeatureIndex.js +++ b/src/mapml/layers/FeatureIndex.js @@ -19,12 +19,10 @@ export var FeatureIndex = L.Layer.extend({ this._output = L.DomUtil.create("output", "mapml-feature-index", map._container); this._body = L.DomUtil.create("span", "mapml-feature-index-content", this._output); - this._moreContent = L.DomUtil.create("span", "mapml-feature-index-more-content", this._output); - this._moreContent.style.display = "none"; map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this); map.on('moveend focus', this._checkOverlap, this); - map.on("keydown", this._toggleMoreContent, this); + map.on("keydown", this._toggleContent, this); this._addOrRemoveFeatureIndex(); }, @@ -48,47 +46,71 @@ export var FeatureIndex = L.Layer.extend({ body.innerHTML = ""; - let moreContent = this._moreContent; - moreContent.innerHTML = ""; - + body.allFeatures = []; keys.forEach(i => { if(layers[i].featureAttributes && featureIndexBounds.overlaps(layers[i]._bounds)){ let label = layers[i].group.getAttribute("aria-label"); - if(index%9 === 0){ - let span = document.createElement("span"); - span.setAttribute("id", index/9); - moreContent.appendChild(span); - if(index === 9){ - body.appendChild(this._updateOutput("More results", 9)); - index += 1; - } + if (index < 8){ + body.appendChild(this._updateOutput(label, index, index)); } - - if(index > 9){ - let value = Math.floor((index - 1)/9); - let span = moreContent.querySelector(`[id=${CSS.escape(value)}]`); - span.appendChild(this._updateOutput(label, index)); - } else { - body.appendChild(this._updateOutput(label, index)); + if (index % 7 === 0 || index === 1) { + body.allFeatures.push([]); + } + body.allFeatures[Math.floor((index - 1) / 7)].push({label, index}); + if (body.allFeatures[1] && body.allFeatures[1].length === 1){ + body.appendChild(this._updateOutput("More results", 0, 9)); } index += 1; - } }); + this._addToggleKeys(); }, - _updateOutput: function (label, index) { + + _updateOutput: function (label, index, key) { let span = document.createElement("span"); - span.innerHTML = `${index}` + " " + label; + span.setAttribute("index", index); + span.innerHTML = `${key}` + " " + label; return span; }, - _toggleMoreContent: function (e){ - let display = this._moreContent.style.display; + + _addToggleKeys: function () { + let allFeatures = this._body.allFeatures; + for(let i = 0; i < allFeatures.length; i++){ + if(allFeatures[i].length === 0) return; + if(allFeatures[i - 1]){ + let label = "Previous results"; + allFeatures[i].push({label}); + } + + if(allFeatures[i + 1] && allFeatures[i + 1].length > 0){ + let label = "More results"; + allFeatures[i].push({label}); + } + } + }, + + _toggleContent: function (e){ + let body = this._body; if(e.originalEvent.keyCode === 57){ - if(display === "none"){ - this._moreContent.style.display = "inline-block"; - } else { - this._moreContent.style.display = "none"; + this._newContent(body, 1); + } else if(e.originalEvent.keyCode === 56){ + this._newContent(body, -1); + } + }, + + _newContent: function (body, direction) { + let index = body.firstChild.getAttribute("index"); + let newContent = body.allFeatures[Math.floor(((index - 1) / 7) + direction)]; + if(newContent && newContent.length > 0){ + body.innerHTML = ""; + for(let i = 0; i < newContent.length; i++){ + let feature = newContent[i]; + let index = feature.index ? feature.index : 0; + let key = i + 1; + if (feature.label === "More results") key = 9; + if (feature.label === "Previous results") key = 8; + body.appendChild(this._updateOutput(feature.label, index, key)); } } }, From b080af220ad4b72539b6a7844998ddc93486102e Mon Sep 17 00:00:00 2001 From: ben-lu-uw <69722289+ben-lu-uw@users.noreply.github.com> Date: Fri, 17 Dec 2021 14:13:19 -0500 Subject: [PATCH 09/31] Optimize the SVG using SVGOMG Co-authored-by: Robert Linder --- src/mapml.css | 2 +- src/mapml/layers/FeatureIndex.js | 30 +++++++++--------------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index 7943ac829..b32daabb7 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -808,7 +808,7 @@ label.mapml-layer-item-toggle { /** * Feature Index */ -.mapml-feature-index-box { +.mapml-feature-index-box:not([hidden]) { margin: -36px 0 0 -36px; width: 72px; height: 72px; diff --git a/src/mapml/layers/FeatureIndex.js b/src/mapml/layers/FeatureIndex.js index 5bb4ff8f8..0595ba633 100644 --- a/src/mapml/layers/FeatureIndex.js +++ b/src/mapml/layers/FeatureIndex.js @@ -1,18 +1,6 @@ export var FeatureIndex = L.Layer.extend({ onAdd: function (map) { - let svgInnerHTML = ` - - - - `; + let svgInnerHTML = ``; this._container = L.DomUtil.create("div", "mapml-feature-index-box", map._container); this._container.innerHTML = svgInnerHTML; @@ -124,17 +112,17 @@ export var FeatureIndex = L.Layer.extend({ let obj = this; setTimeout(function() { if (e && e.type === "focus") { - obj._container.querySelector('rect').style.display = "inline"; - obj._output.style.display = "block"; + obj._container.removeAttribute("hidden"); + obj._output.removeAttribute("hidden"); } else if (e && e.type === "blur") { - obj._container.querySelector('rect').style.display = "none"; - obj._output.style.display = "none"; + obj._container.setAttribute("hidden", ""); + obj._output.setAttribute("hidden", ""); } else if (obj._map.isFocused) { - obj._container.querySelector('rect').style.display = "inline"; - obj._output.style.display = "block"; + obj._container.removeAttribute("hidden"); + obj._output.removeAttribute("hidden"); } else { - obj._container.querySelector('rect').style.display = "none"; - obj._output.style.display = "none"; + obj._container.setAttribute("hidden", ""); + obj._output.setAttribute("hidden", ""); } }, 0); From a7f1af051ed8ed53e94cdfc3fe7df79f8a95e53f Mon Sep 17 00:00:00 2001 From: ben-lu-uw <69722289+ben-lu-uw@users.noreply.github.com> Date: Fri, 17 Dec 2021 15:02:47 -0500 Subject: [PATCH 10/31] Optimize the SVG using SVGOMG Co-authored-by: Robert Linder --- src/mapml.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mapml.css b/src/mapml.css index b32daabb7..4bd776f76 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -820,7 +820,7 @@ label.mapml-layer-item-toggle { z-index: 10000; } -.mapml-feature-index { +.mapml-feature-index:not([hidden]) { contain: content; max-height: 100%; max-width: 100%; From 7e361ef3bab5dc06d8f572f19d896d32731cee9c Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Fri, 17 Dec 2021 15:27:07 -0500 Subject: [PATCH 11/31] Hide output when empty --- src/mapml/layers/FeatureIndex.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mapml/layers/FeatureIndex.js b/src/mapml/layers/FeatureIndex.js index 0595ba633..b5937acee 100644 --- a/src/mapml/layers/FeatureIndex.js +++ b/src/mapml/layers/FeatureIndex.js @@ -110,16 +110,21 @@ export var FeatureIndex = L.Layer.extend({ _addOrRemoveFeatureIndex: function (e) { let obj = this; + let features = this._body.allFeatures ? this._body.allFeatures.length : 0; setTimeout(function() { if (e && e.type === "focus") { obj._container.removeAttribute("hidden"); - obj._output.removeAttribute("hidden"); + if (features !== 0) obj._output.removeAttribute("hidden"); } else if (e && e.type === "blur") { obj._container.setAttribute("hidden", ""); obj._output.setAttribute("hidden", ""); } else if (obj._map.isFocused) { obj._container.removeAttribute("hidden"); - obj._output.removeAttribute("hidden"); + if (features !== 0) { + obj._output.removeAttribute("hidden"); + } else { + obj._output.setAttribute("hidden", ""); + } } else { obj._container.setAttribute("hidden", ""); obj._output.setAttribute("hidden", ""); From 64fb95f452caeef0516db47d32ffe91ccc7b2159 Mon Sep 17 00:00:00 2001 From: ben-lu-uw <69722289+ben-lu-uw@users.noreply.github.com> Date: Fri, 17 Dec 2021 15:29:24 -0500 Subject: [PATCH 12/31] Adding thin outline to reticle Co-authored-by: Robert Linder --- src/mapml.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mapml.css b/src/mapml.css index 4bd776f76..fd5f709a2 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -818,6 +818,7 @@ label.mapml-layer-item-toggle { display: block; position: absolute; z-index: 10000; + outline: thin solid #fff; } .mapml-feature-index:not([hidden]) { From ef177f9f8ae5a1c4974061f0613e4c394248242a Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Sun, 19 Dec 2021 22:24:38 -0500 Subject: [PATCH 13/31] Focus on feature when number key is pressed --- src/mapml/layers/FeatureIndex.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/mapml/layers/FeatureIndex.js b/src/mapml/layers/FeatureIndex.js index b5937acee..adea333e5 100644 --- a/src/mapml/layers/FeatureIndex.js +++ b/src/mapml/layers/FeatureIndex.js @@ -7,10 +7,11 @@ export var FeatureIndex = L.Layer.extend({ this._output = L.DomUtil.create("output", "mapml-feature-index", map._container); this._body = L.DomUtil.create("span", "mapml-feature-index-content", this._output); + this._body.index = 0; map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this); map.on('moveend focus', this._checkOverlap, this); - map.on("keydown", this._toggleContent, this); + map.on("keydown", this._onKeyDown, this); this._addOrRemoveFeatureIndex(); }, @@ -33,11 +34,13 @@ export var FeatureIndex = L.Layer.extend({ let body = this._body; body.innerHTML = ""; + body.index = 0; body.allFeatures = []; keys.forEach(i => { if(layers[i].featureAttributes && featureIndexBounds.overlaps(layers[i]._bounds)){ - let label = layers[i].group.getAttribute("aria-label"); + let group = layers[i].group; + let label = group.getAttribute("aria-label"); if (index < 8){ body.appendChild(this._updateOutput(label, index, index)); @@ -45,7 +48,7 @@ export var FeatureIndex = L.Layer.extend({ if (index % 7 === 0 || index === 1) { body.allFeatures.push([]); } - body.allFeatures[Math.floor((index - 1) / 7)].push({label, index}); + body.allFeatures[Math.floor((index - 1) / 7)].push({label, index, group}); if (body.allFeatures[1] && body.allFeatures[1].length === 1){ body.appendChild(this._updateOutput("More results", 0, 9)); } @@ -78,12 +81,16 @@ export var FeatureIndex = L.Layer.extend({ } }, - _toggleContent: function (e){ + _onKeyDown: function (e){ let body = this._body; - if(e.originalEvent.keyCode === 57){ - this._newContent(body, 1); + let key = e.originalEvent.keyCode; + if (key >= 49 && key <= 55){ + let group = body.allFeatures[body.index][key - 49].group; + if (group) group.focus(); } else if(e.originalEvent.keyCode === 56){ this._newContent(body, -1); + } else if(e.originalEvent.keyCode === 57){ + this._newContent(body, 1); } }, @@ -92,6 +99,7 @@ export var FeatureIndex = L.Layer.extend({ let newContent = body.allFeatures[Math.floor(((index - 1) / 7) + direction)]; if(newContent && newContent.length > 0){ body.innerHTML = ""; + body.index += direction; for(let i = 0; i < newContent.length; i++){ let feature = newContent[i]; let index = feature.index ? feature.index : 0; From 6d02a1ad093f52baee6e354f2e7be9914b3cfd82 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Mon, 20 Dec 2021 11:22:47 -0500 Subject: [PATCH 14/31] Refactor code --- src/mapml-viewer.js | 2 +- src/mapml.css | 4 ++-- src/mapml/index.js | 6 ++--- ...FeatureIndex.js => FeatureIndexOverlay.js} | 23 +++++++++++++------ src/web-map.js | 2 +- 5 files changed, 23 insertions(+), 14 deletions(-) rename src/mapml/layers/{FeatureIndex.js => FeatureIndexOverlay.js} (88%) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index b9c59b066..789490fa6 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -219,7 +219,7 @@ export class MapViewer extends HTMLElement { this.setControls(false,false,true); this._crosshair = M.crosshair().addTo(this._map); - this._featureIndex = M.featureIndex().addTo(this._map); + this._featureIndexOverlay = M.featureIndexOverlay().addTo(this._map); // https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/274 this.setAttribute('role', 'application'); // Make the Leaflet container element programmatically identifiable diff --git a/src/mapml.css b/src/mapml.css index fd5f709a2..ed415732e 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -808,7 +808,7 @@ label.mapml-layer-item-toggle { /** * Feature Index */ -.mapml-feature-index-box:not([hidden]) { +.mapml-feature-index-box { margin: -36px 0 0 -36px; width: 72px; height: 72px; @@ -821,7 +821,7 @@ label.mapml-layer-item-toggle { outline: thin solid #fff; } -.mapml-feature-index:not([hidden]) { +.mapml-feature-index { contain: content; max-height: 100%; max-width: 100%; diff --git a/src/mapml/index.js b/src/mapml/index.js index ec401e131..7f4ae9fca 100644 --- a/src/mapml/index.js +++ b/src/mapml/index.js @@ -61,7 +61,7 @@ import {AnnounceMovement} from "./handlers/AnnounceMovement"; import { FeatureIndex } from "./handlers/FeatureIndex"; import { Options } from "./options"; import "./keyboard"; -import {featureIndex, FeatureIndex} from "./layers/FeatureIndex"; +import {featureIndexOverlay, FeatureIndexOverlay} from "./layers/FeatureIndexOverlay"; /* global L, Node */ (function (window, document, undefined) { @@ -656,8 +656,8 @@ M.debugOverlay = debugOverlay; M.Crosshair = Crosshair; M.crosshair = crosshair; -M.FeatureIndex = FeatureIndex; -M.featureIndex = featureIndex; +M.FeatureIndexOverlay = FeatureIndexOverlay; +M.featureIndexOverlay = featureIndexOverlay; M.Feature = Feature; M.feature = feature; diff --git a/src/mapml/layers/FeatureIndex.js b/src/mapml/layers/FeatureIndexOverlay.js similarity index 88% rename from src/mapml/layers/FeatureIndex.js rename to src/mapml/layers/FeatureIndexOverlay.js index adea333e5..e8787237f 100644 --- a/src/mapml/layers/FeatureIndex.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -1,4 +1,4 @@ -export var FeatureIndex = L.Layer.extend({ +export var FeatureIndexOverlay = L.Layer.extend({ onAdd: function (map) { let svgInnerHTML = ``; @@ -28,9 +28,9 @@ export var FeatureIndex = L.Layer.extend({ let b = L.bounds(minPoint, maxPoint); let featureIndexBounds = M.pixelToPCRSBounds(b,this._map.getZoom(),this._map.options.projection); - let layers = this._map._layers; + let features = this._map.featureIndex.inBoundFeatures; let index = 1; - let keys = Object.keys(layers); + let keys = Object.keys(features); let body = this._body; body.innerHTML = ""; @@ -38,8 +38,17 @@ export var FeatureIndex = L.Layer.extend({ body.allFeatures = []; keys.forEach(i => { - if(layers[i].featureAttributes && featureIndexBounds.overlaps(layers[i]._bounds)){ - let group = layers[i].group; + let layers = features[i].layer._layers; + let keys = Object.keys(layers); + let bounds = L.bounds(); + keys.forEach(j => { + if(!bounds) bounds = L.bounds(layers[j]._bounds.min, layers[j]._bounds.max); + bounds.extend(layers[j]._bounds.min); + bounds.extend(layers[j]._bounds.max); + }); + + if(featureIndexBounds.overlaps(bounds)){ + let group = features[i].path; let label = group.getAttribute("aria-label"); if (index < 8){ @@ -143,6 +152,6 @@ export var FeatureIndex = L.Layer.extend({ }); -export var featureIndex = function (options) { - return new FeatureIndex(options); +export var featureIndexOverlay = function (options) { + return new FeatureIndexOverlay(options); }; \ No newline at end of file diff --git a/src/web-map.js b/src/web-map.js index 7cb67b14b..64f202b46 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -234,7 +234,7 @@ export class WebMap extends HTMLMapElement { this.setControls(false,false,true); this._crosshair = M.crosshair().addTo(this._map); - this._featureIndex = M.featureIndex().addTo(this._map); + this._featureIndexOverlay = M.featureIndexOverlay().addTo(this._map); if (this.hasAttribute('name')) { var name = this.getAttribute('name'); From bd9ac2a751a94f72f665a1eb998b7bbfcca75ff9 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Mon, 20 Dec 2021 16:05:27 -0500 Subject: [PATCH 15/31] Fix focusing on feature when number key is pressed --- src/mapml/features/featureGroup.js | 4 ++-- src/mapml/layers/FeatureIndexOverlay.js | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/mapml/features/featureGroup.js b/src/mapml/features/featureGroup.js index 88bfc661e..6968e36c5 100644 --- a/src/mapml/features/featureGroup.js +++ b/src/mapml/features/featureGroup.js @@ -79,13 +79,13 @@ export var FeatureGroup = L.FeatureGroup.extend({ this._map.featureIndex.inBoundFeatures[index].path.setAttribute("tabindex", 0); } } - } else if (!([9, 16, 13, 27].includes(e.keyCode))){ + } else if (!([9, 16, 13, 27, 49, 50, 51, 52, 53, 54, 55].includes(e.keyCode))){ this._map.featureIndex.currentIndex = 0; this._map.featureIndex.inBoundFeatures[0].path.focus(); } if(e.target.tagName.toUpperCase() !== "G") return; - if((e.keyCode === 9 || e.keyCode === 16 || e.keyCode === 13) && e.type === "keyup") { + if((e.keyCode === 9 || e.keyCode === 16 || e.keyCode === 13 || (e.keyCode >= 49 && e.keyCode <= 55)) && e.type === "keyup") { this.openTooltip(); } else if (e.keyCode === 13 || e.keyCode === 32){ this.closeTooltip(); diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index e8787237f..a31601bd8 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -16,6 +16,7 @@ export var FeatureIndexOverlay = L.Layer.extend({ }, _checkOverlap: function () { + this._map.fire("mapkeyboardfocused"); let bounds = this._map.getPixelBounds(); let center = bounds.getCenter(); let wRatio = Math.abs(bounds.min.x - bounds.max.x) / (this._map.options.mapEl.width); @@ -94,8 +95,12 @@ export var FeatureIndexOverlay = L.Layer.extend({ let body = this._body; let key = e.originalEvent.keyCode; if (key >= 49 && key <= 55){ - let group = body.allFeatures[body.index][key - 49].group; - if (group) group.focus(); + let feature = body.allFeatures[body.index][key - 49]; + let group = feature.group; + if (group) { + this._map.featureIndex.currentIndex = feature.index - 1; + group.focus(); + } } else if(e.originalEvent.keyCode === 56){ this._newContent(body, -1); } else if(e.originalEvent.keyCode === 57){ From ef0d3041a68e6ab0bd3e12323745f0c3ca4f16ae Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Tue, 21 Dec 2021 11:12:22 -0500 Subject: [PATCH 16/31] Fix failing tests --- src/mapml.css | 1 + src/mapml/layers/FeatureIndexOverlay.js | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index ed415732e..8e4329ebd 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -430,6 +430,7 @@ } /* Disable pointer events where they'd interfere with the intended action. */ +.mapml-feature-index-box, .leaflet-tooltip, .leaflet-crosshair *, .mapml-layer-item-settings .mapml-control-layers summary label, diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index a31601bd8..0b58dc5b7 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -40,13 +40,18 @@ export var FeatureIndexOverlay = L.Layer.extend({ body.allFeatures = []; keys.forEach(i => { let layers = features[i].layer._layers; - let keys = Object.keys(layers); let bounds = L.bounds(); - keys.forEach(j => { - if(!bounds) bounds = L.bounds(layers[j]._bounds.min, layers[j]._bounds.max); - bounds.extend(layers[j]._bounds.min); - bounds.extend(layers[j]._bounds.max); - }); + + if(layers) { + let keys = Object.keys(layers); + keys.forEach(j => { + if(!bounds) bounds = L.bounds(layers[j]._bounds.min, layers[j]._bounds.max); + bounds.extend(layers[j]._bounds.min); + bounds.extend(layers[j]._bounds.max); + }); + } else if(features[i].layer._bounds){ + bounds = L.bounds(features[i].layer._bounds.min, features[i].layer._bounds.max); + } if(featureIndexBounds.overlaps(bounds)){ let group = features[i].path; From b6ec7ceeef6c8fa80d0d6f42b24a703bc0140e71 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Tue, 21 Dec 2021 11:59:39 -0500 Subject: [PATCH 17/31] Add feature index overlay tests --- test/e2e/core/featureIndexOverlay.html | 18 +++++ test/e2e/core/featureIndexOverlay.test.js | 88 +++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 test/e2e/core/featureIndexOverlay.html create mode 100644 test/e2e/core/featureIndexOverlay.test.js diff --git a/test/e2e/core/featureIndexOverlay.html b/test/e2e/core/featureIndexOverlay.html new file mode 100644 index 000000000..4b29516ae --- /dev/null +++ b/test/e2e/core/featureIndexOverlay.html @@ -0,0 +1,18 @@ + + + + + Feature Index Overlay + + + + + + + + + + + + \ No newline at end of file diff --git a/test/e2e/core/featureIndexOverlay.test.js b/test/e2e/core/featureIndexOverlay.test.js new file mode 100644 index 000000000..28a650630 --- /dev/null +++ b/test/e2e/core/featureIndexOverlay.test.js @@ -0,0 +1,88 @@ +describe("Announce movement test", ()=> { + beforeAll(async () => { + await page.goto(PATH + "featureIndexOverlay.html"); + }); + + afterAll(async function () { + await context.close(); + }); + + test("Feature index overlay and reticle shows on focus", async () => { + const hiddenOverlay = await page.$eval( + "div > output.mapml-feature-index", + (output) => output.hasAttribute("hidden") + ); + const hiddenReticle = await page.$eval( + "div > div.mapml-feature-index-box", + (div) => div.hasAttribute("hidden") + ); + + await page.keyboard.press("Tab"); + await page.waitForTimeout(500); + const afterTabOverlay = await page.$eval( + "div > output.mapml-feature-index", + (output) => output.hasAttribute("hidden") + ); + const afterTabReticle = await page.$eval( + "div > div.mapml-feature-index-box", + (div) => div.hasAttribute("hidden") + ); + + await expect(hiddenOverlay).toEqual(true); + await expect(hiddenReticle).toEqual(true); + await expect(afterTabOverlay).toEqual(false); + await expect(afterTabReticle).toEqual(false); + }); + + test("Feature index content is correct", async () => { + const spanCount = await page.$eval( + "div > output.mapml-feature-index > span", + (span) => span.childElementCount + ); + const firstFeature = await page.$eval( + "div > output.mapml-feature-index > span > span:nth-child(1)", + (span) => span.innerText + ); + const lastSpan = await page.$eval( + "div > output.mapml-feature-index > span > span:nth-child(8)", + (span) => span.innerText + ); + + await expect(spanCount).toEqual(8); + await expect(firstFeature).toEqual("1 Vermont"); + await expect(lastSpan).toEqual("9 More results"); + }); + + test("Feature index more results are correct", async () => { + await page.keyboard.press("9"); + await page.waitForTimeout(500); + + const spanCount = await page.$eval( + "div > output.mapml-feature-index > span", + (span) => span.childElementCount + ); + const firstFeature = await page.$eval( + "div > output.mapml-feature-index > span > span:nth-child(1)", + (span) => span.innerText + ); + const lastSpan = await page.$eval( + "div > output.mapml-feature-index > span > span:nth-child(3)", + (span) => span.innerText + ); + + await expect(spanCount).toEqual(3); + await expect(firstFeature).toEqual("1 Pennsylvania"); + await expect(lastSpan).toEqual("8 Previous results"); + }); + + test("Feature index previous results are correct", async () => { + await page.keyboard.press("8"); + const spanCount = await page.$eval( + "div > output.mapml-feature-index > span", + (span) => span.childElementCount + ); + + await expect(spanCount).toEqual(8); + }); + +}); \ No newline at end of file From ecd0a5bb35bd6bc58992e694a06d301e73e1c80d Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Tue, 21 Dec 2021 12:50:37 -0500 Subject: [PATCH 18/31] Make esc key return focus to leaflet container --- src/mapml/features/featureGroup.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mapml/features/featureGroup.js b/src/mapml/features/featureGroup.js index 6968e36c5..1ec50019d 100644 --- a/src/mapml/features/featureGroup.js +++ b/src/mapml/features/featureGroup.js @@ -82,6 +82,9 @@ export var FeatureGroup = L.FeatureGroup.extend({ } else if (!([9, 16, 13, 27, 49, 50, 51, 52, 53, 54, 55].includes(e.keyCode))){ this._map.featureIndex.currentIndex = 0; this._map.featureIndex.inBoundFeatures[0].path.focus(); + } else if(e.keyCode === 27){ + this._map.featureIndex.currentIndex = 0; + this._map._container.focus(); } if(e.target.tagName.toUpperCase() !== "G") return; From 464969ed73599403d47cfecaa4e30b112f426c78 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Wed, 22 Dec 2021 11:41:34 -0500 Subject: [PATCH 19/31] Increase reticle size, update tests --- src/mapml.css | 8 ++-- src/mapml/layers/FeatureIndexOverlay.js | 10 ++--- test/e2e/core/featureIndexOverlay.test.js | 45 ++++++++++++++++++++--- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index 8e4329ebd..31f665fb5 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -810,16 +810,16 @@ label.mapml-layer-item-toggle { * Feature Index */ .mapml-feature-index-box { - margin: -36px 0 0 -36px; - width: 72px; - height: 72px; + margin: -60px 0 0 -60px; + width: 120px; + height: 120px; left: 50%; top: 50%; content: ''; display: block; position: absolute; z-index: 10000; - outline: thin solid #fff; + outline: 2px solid #fff; } .mapml-feature-index { diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index 0b58dc5b7..f313c34ab 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -1,6 +1,6 @@ export var FeatureIndexOverlay = L.Layer.extend({ onAdd: function (map) { - let svgInnerHTML = ``; + let svgInnerHTML = ``; this._container = L.DomUtil.create("div", "mapml-feature-index-box", map._container); this._container.innerHTML = svgInnerHTML; @@ -22,8 +22,8 @@ export var FeatureIndexOverlay = L.Layer.extend({ let wRatio = Math.abs(bounds.min.x - bounds.max.x) / (this._map.options.mapEl.width); let hRatio = Math.abs(bounds.min.y - bounds.max.y) / (this._map.options.mapEl.height); - let w = wRatio * 36; - let h = hRatio * 36; + let w = wRatio * (getComputedStyle(this._container).width).replace(/\D/g,'') / 2; + let h = hRatio * (getComputedStyle(this._container).height).replace(/\D/g,'') / 2; let minPoint = L.point(center.x - w, center.y + h); let maxPoint = L.point(center.x + w, center.y - h); let b = L.bounds(minPoint, maxPoint); @@ -106,9 +106,9 @@ export var FeatureIndexOverlay = L.Layer.extend({ this._map.featureIndex.currentIndex = feature.index - 1; group.focus(); } - } else if(e.originalEvent.keyCode === 56){ + } else if(key === 56){ this._newContent(body, -1); - } else if(e.originalEvent.keyCode === 57){ + } else if(key === 57){ this._newContent(body, 1); } }, diff --git a/test/e2e/core/featureIndexOverlay.test.js b/test/e2e/core/featureIndexOverlay.test.js index 28a650630..a90386f73 100644 --- a/test/e2e/core/featureIndexOverlay.test.js +++ b/test/e2e/core/featureIndexOverlay.test.js @@ -43,14 +43,14 @@ describe("Announce movement test", ()=> { "div > output.mapml-feature-index > span > span:nth-child(1)", (span) => span.innerText ); - const lastSpan = await page.$eval( + const moreResults = await page.$eval( "div > output.mapml-feature-index > span > span:nth-child(8)", (span) => span.innerText ); await expect(spanCount).toEqual(8); await expect(firstFeature).toEqual("1 Vermont"); - await expect(lastSpan).toEqual("9 More results"); + await expect(moreResults).toEqual("9 More results"); }); test("Feature index more results are correct", async () => { @@ -65,14 +65,14 @@ describe("Announce movement test", ()=> { "div > output.mapml-feature-index > span > span:nth-child(1)", (span) => span.innerText ); - const lastSpan = await page.$eval( - "div > output.mapml-feature-index > span > span:nth-child(3)", + const prevResults = await page.$eval( + "div > output.mapml-feature-index > span > span:nth-child(8)", (span) => span.innerText ); - await expect(spanCount).toEqual(3); + await expect(spanCount).toEqual(9); await expect(firstFeature).toEqual("1 Pennsylvania"); - await expect(lastSpan).toEqual("8 Previous results"); + await expect(prevResults).toEqual("8 Previous results"); }); test("Feature index previous results are correct", async () => { @@ -85,4 +85,37 @@ describe("Announce movement test", ()=> { await expect(spanCount).toEqual(8); }); + test("Feature index content is correct on moveend", async () => { + await page.keyboard.press("ArrowUp"); + await page.waitForTimeout(1000); + const spanCount = await page.$eval( + "div > output.mapml-feature-index > span", + (span) => span.childElementCount + ); + const firstFeature = await page.$eval( + "div > output.mapml-feature-index > span > span:nth-child(1)", + (span) => span.innerText + ); + + await expect(spanCount).toEqual(1); + await expect(firstFeature).toEqual("1 Maine"); + }); + + test("Feature index overlay is hidden when empty, reticle still visible", async () => { + await page.keyboard.press("ArrowUp"); + await page.waitForTimeout(1000); + + const overlay = await page.$eval( + "div > output.mapml-feature-index", + (output) => output.hasAttribute("hidden") + ); + const reticle = await page.$eval( + "div > div.mapml-feature-index-box", + (div) => div.hasAttribute("hidden") + ); + + await expect(overlay).toEqual(true); + await expect(reticle).toEqual(false); + }); + }); \ No newline at end of file From 04db6971584a27cb6b09bd43e6fc549b1ceb8bcc Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Thu, 23 Dec 2021 14:15:58 -0500 Subject: [PATCH 20/31] Update feature index overlay output --- src/mapml/layers/FeatureIndexOverlay.js | 13 ++++++++----- test/e2e/core/featureIndexOverlay.test.js | 6 +++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index f313c34ab..8360e8434 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -6,6 +6,9 @@ export var FeatureIndexOverlay = L.Layer.extend({ this._container.innerHTML = svgInnerHTML; this._output = L.DomUtil.create("output", "mapml-feature-index", map._container); + this._output.setAttribute("role", "status"); + this._output.setAttribute("aria-live", "polite"); + this._output.setAttribute("aria-atomic", "true"); this._body = L.DomUtil.create("span", "mapml-feature-index-content", this._output); this._body.index = 0; @@ -141,20 +144,20 @@ export var FeatureIndexOverlay = L.Layer.extend({ setTimeout(function() { if (e && e.type === "focus") { obj._container.removeAttribute("hidden"); - if (features !== 0) obj._output.removeAttribute("hidden"); + if (features !== 0) obj._output.classList.remove("mapml-screen-reader-output"); } else if (e && e.type === "blur") { obj._container.setAttribute("hidden", ""); - obj._output.setAttribute("hidden", ""); + obj._output.classList.add("mapml-screen-reader-output"); } else if (obj._map.isFocused) { obj._container.removeAttribute("hidden"); if (features !== 0) { - obj._output.removeAttribute("hidden"); + obj._output.classList.remove("mapml-screen-reader-output"); } else { - obj._output.setAttribute("hidden", ""); + obj._output.classList.add("mapml-screen-reader-output"); } } else { obj._container.setAttribute("hidden", ""); - obj._output.setAttribute("hidden", ""); + obj._output.classList.add("mapml-screen-reader-output"); } }, 0); diff --git a/test/e2e/core/featureIndexOverlay.test.js b/test/e2e/core/featureIndexOverlay.test.js index a90386f73..1dfe748fd 100644 --- a/test/e2e/core/featureIndexOverlay.test.js +++ b/test/e2e/core/featureIndexOverlay.test.js @@ -10,7 +10,7 @@ describe("Announce movement test", ()=> { test("Feature index overlay and reticle shows on focus", async () => { const hiddenOverlay = await page.$eval( "div > output.mapml-feature-index", - (output) => output.hasAttribute("hidden") + (output) => output.classList.contains("mapml-screen-reader-output") ); const hiddenReticle = await page.$eval( "div > div.mapml-feature-index-box", @@ -21,7 +21,7 @@ describe("Announce movement test", ()=> { await page.waitForTimeout(500); const afterTabOverlay = await page.$eval( "div > output.mapml-feature-index", - (output) => output.hasAttribute("hidden") + (output) => output.classList.contains("mapml-screen-reader-output") ); const afterTabReticle = await page.$eval( "div > div.mapml-feature-index-box", @@ -107,7 +107,7 @@ describe("Announce movement test", ()=> { const overlay = await page.$eval( "div > output.mapml-feature-index", - (output) => output.hasAttribute("hidden") + (output) => output.classList.contains("mapml-screen-reader-output") ); const reticle = await page.$eval( "div > div.mapml-feature-index-box", From 4445e698c8188ba71eba23cf136c783be375caae Mon Sep 17 00:00:00 2001 From: ben-lu-uw <69722289+ben-lu-uw@users.noreply.github.com> Date: Mon, 2 May 2022 13:57:52 -0400 Subject: [PATCH 21/31] change feature index font Co-authored-by: Robert Linder --- src/mapml.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mapml.css b/src/mapml.css index 31f665fb5..3e7a42cca 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -844,6 +844,5 @@ label.mapml-layer-item-toggle { } .mapml-feature-index-content > span{ display: block; - font-family: monospace; } From a79526495862e5fd331b31683ed045178cf643d5 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Mon, 2 May 2022 14:03:22 -0400 Subject: [PATCH 22/31] Update span attribute --- src/mapml/layers/FeatureIndexOverlay.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index 8360e8434..cd8cd4f60 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -78,7 +78,7 @@ export var FeatureIndexOverlay = L.Layer.extend({ _updateOutput: function (label, index, key) { let span = document.createElement("span"); - span.setAttribute("index", index); + span.setAttribute("data-index", index); span.innerHTML = `${key}` + " " + label; return span; }, @@ -117,7 +117,7 @@ export var FeatureIndexOverlay = L.Layer.extend({ }, _newContent: function (body, direction) { - let index = body.firstChild.getAttribute("index"); + let index = body.firstChild.getAttribute("data-index"); let newContent = body.allFeatures[Math.floor(((index - 1) / 7) + direction)]; if(newContent && newContent.length > 0){ body.innerHTML = ""; From e6bec6a41a1e7119ae9d2e92ad6533a3cf7f34a4 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Tue, 3 May 2022 15:29:27 -0400 Subject: [PATCH 23/31] Add feature index overlay option --- src/mapml-viewer.js | 2 +- src/mapml/options.js | 1 + src/web-map.js | 2 +- test/e2e/core/featureIndexOverlay.html | 5 +++++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 789490fa6..3c5e9e100 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -219,7 +219,7 @@ export class MapViewer extends HTMLElement { this.setControls(false,false,true); this._crosshair = M.crosshair().addTo(this._map); - this._featureIndexOverlay = M.featureIndexOverlay().addTo(this._map); + if(M.options.featureIndexOverlayOption) this._featureIndexOverlay = M.featureIndexOverlay().addTo(this._map); // https://github.com/Maps4HTML/Web-Map-Custom-Element/issues/274 this.setAttribute('role', 'application'); // Make the Leaflet container element programmatically identifiable diff --git a/src/mapml/options.js b/src/mapml/options.js index 72785bf38..c8ed01e23 100644 --- a/src/mapml/options.js +++ b/src/mapml/options.js @@ -1,4 +1,5 @@ export var Options = { + featureIndexOverlayOption: false, announceMovement: false, locale: { cmBack: "Back", diff --git a/src/web-map.js b/src/web-map.js index 64f202b46..117f93b4a 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -234,7 +234,7 @@ export class WebMap extends HTMLMapElement { this.setControls(false,false,true); this._crosshair = M.crosshair().addTo(this._map); - this._featureIndexOverlay = M.featureIndexOverlay().addTo(this._map); + if(M.options.featureIndexOverlayOption) this._featureIndexOverlay = M.featureIndexOverlay().addTo(this._map); if (this.hasAttribute('name')) { var name = this.getAttribute('name'); diff --git a/test/e2e/core/featureIndexOverlay.html b/test/e2e/core/featureIndexOverlay.html index 4b29516ae..5820dd875 100644 --- a/test/e2e/core/featureIndexOverlay.html +++ b/test/e2e/core/featureIndexOverlay.html @@ -5,6 +5,11 @@ Feature Index Overlay + From c2c88e8aeaa99f03a2c4b50a3eb9666bdc0833a8 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Tue, 3 May 2022 19:47:36 -0400 Subject: [PATCH 24/31] Fix cs of us_pop_density.mapml --- test/e2e/data/tiles/cbmt/us_pop_density.mapml | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/data/tiles/cbmt/us_pop_density.mapml b/test/e2e/data/tiles/cbmt/us_pop_density.mapml index cb143e9d0..7304af562 100644 --- a/test/e2e/data/tiles/cbmt/us_pop_density.mapml +++ b/test/e2e/data/tiles/cbmt/us_pop_density.mapml @@ -3,6 +3,7 @@ US Population Density + Date: Mon, 9 May 2022 09:23:58 -0400 Subject: [PATCH 25/31] Improve feature index styling for visual users (#1) * Make reticle responsive * Open popup instead of focusing * Static overlay dimensions * Improve readability of feature index * Focus feature if no popup is available * Update featureIndexOverlay.test.js to account for new reticle size --- src/mapml.css | 55 ++++++++++++++++------- src/mapml/layers/FeatureIndexOverlay.js | 31 +++++++------ test/e2e/core/featureIndexOverlay.test.js | 10 ++--- 3 files changed, 61 insertions(+), 35 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index 3e7a42cca..241e12e8b 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -810,39 +810,60 @@ label.mapml-layer-item-toggle { * Feature Index */ .mapml-feature-index-box { - margin: -60px 0 0 -60px; - width: 120px; - height: 120px; + margin: -8% 0 0 -8%; + width: 16%; left: 50%; top: 50%; - content: ''; - display: block; position: absolute; z-index: 10000; outline: 2px solid #fff; } +.mapml-feature-index-box:after{ + display: block; + content: ''; + padding-top: 100%; +} + +.mapml-feature-index-box > svg { + position: absolute; + width: 100%; + height: 100%; +} + .mapml-feature-index { + outline: 1px solid #000000; contain: content; - max-height: 100%; - max-width: 100%; border-radius: 4px; background-color: #fff; cursor: default; z-index: 1000; position: absolute; - display: block; top: auto; - right: 5px; - bottom: 5px; - left: 5px; - padding: 5px 10px; - width: -webkit-fit-content; - width: -moz-fit-content; - width: fit-content; - font: inherit; + left: 50%; + -ms-transform: translateX(-50%); + transform: translateX(-50%); + bottom: 30px; + padding-top: 5px; + height: 92px; + width: 450px; + font-size: 16px; } + .mapml-feature-index-content > span{ - display: block; + width: 140px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + padding-left: 5px; + padding-right: 5px; +} + +.mapml-feature-index-content > span > kbd{ + background-color: lightgrey; + padding-right: 4px; + padding-left: 4px; + border-radius: 4px; } diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index cd8cd4f60..cb7c11057 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -25,8 +25,12 @@ export var FeatureIndexOverlay = L.Layer.extend({ let wRatio = Math.abs(bounds.min.x - bounds.max.x) / (this._map.options.mapEl.width); let hRatio = Math.abs(bounds.min.y - bounds.max.y) / (this._map.options.mapEl.height); - let w = wRatio * (getComputedStyle(this._container).width).replace(/\D/g,'') / 2; - let h = hRatio * (getComputedStyle(this._container).height).replace(/\D/g,'') / 2; + let reticleDimension = (getComputedStyle(this._container).width).replace(/[^\d.]/g,''); + if((getComputedStyle(this._container).width).slice(-1) === "%") { + reticleDimension = reticleDimension * this._map.options.mapEl.width / 100; + } + let w = wRatio * reticleDimension / 2; + let h = hRatio * reticleDimension / 2; let minPoint = L.point(center.x - w, center.y + h); let maxPoint = L.point(center.x + w, center.y - h); let b = L.bounds(minPoint, maxPoint); @@ -42,23 +46,23 @@ export var FeatureIndexOverlay = L.Layer.extend({ body.allFeatures = []; keys.forEach(i => { + let layer = features[i].layer; let layers = features[i].layer._layers; let bounds = L.bounds(); if(layers) { let keys = Object.keys(layers); keys.forEach(j => { - if(!bounds) bounds = L.bounds(layers[j]._bounds.min, layers[j]._bounds.max); - bounds.extend(layers[j]._bounds.min); - bounds.extend(layers[j]._bounds.max); + if(!bounds) bounds = L.bounds(layer._layers[j]._bounds.min, layer._layers[j]._bounds.max); + bounds.extend(layer._layers[j]._bounds.min); + bounds.extend(layer._layers[j]._bounds.max); }); - } else if(features[i].layer._bounds){ - bounds = L.bounds(features[i].layer._bounds.min, features[i].layer._bounds.max); + } else if(layer._bounds){ + bounds = L.bounds(layer._bounds.min, layer._bounds.max); } if(featureIndexBounds.overlaps(bounds)){ - let group = features[i].path; - let label = group.getAttribute("aria-label"); + let label = features[i].path.getAttribute("aria-label"); if (index < 8){ body.appendChild(this._updateOutput(label, index, index)); @@ -66,7 +70,7 @@ export var FeatureIndexOverlay = L.Layer.extend({ if (index % 7 === 0 || index === 1) { body.allFeatures.push([]); } - body.allFeatures[Math.floor((index - 1) / 7)].push({label, index, group}); + body.allFeatures[Math.floor((index - 1) / 7)].push({label, index, layer}); if (body.allFeatures[1] && body.allFeatures[1].length === 1){ body.appendChild(this._updateOutput("More results", 0, 9)); } @@ -104,10 +108,11 @@ export var FeatureIndexOverlay = L.Layer.extend({ let key = e.originalEvent.keyCode; if (key >= 49 && key <= 55){ let feature = body.allFeatures[body.index][key - 49]; - let group = feature.group; - if (group) { + let layer = feature.layer; + if (layer) { this._map.featureIndex.currentIndex = feature.index - 1; - group.focus(); + if (layer._popup) layer.openPopup(); + else layer.options.group.focus(); } } else if(key === 56){ this._newContent(body, -1); diff --git a/test/e2e/core/featureIndexOverlay.test.js b/test/e2e/core/featureIndexOverlay.test.js index 1dfe748fd..8b855e21d 100644 --- a/test/e2e/core/featureIndexOverlay.test.js +++ b/test/e2e/core/featureIndexOverlay.test.js @@ -1,4 +1,4 @@ -describe("Announce movement test", ()=> { +describe("Feature Index Overlay test", ()=> { beforeAll(async () => { await page.goto(PATH + "featureIndexOverlay.html"); }); @@ -66,11 +66,11 @@ describe("Announce movement test", ()=> { (span) => span.innerText ); const prevResults = await page.$eval( - "div > output.mapml-feature-index > span > span:nth-child(8)", + "div > output.mapml-feature-index > span > span:nth-child(5)", (span) => span.innerText ); - await expect(spanCount).toEqual(9); + await expect(spanCount).toEqual(5); await expect(firstFeature).toEqual("1 Pennsylvania"); await expect(prevResults).toEqual("8 Previous results"); }); @@ -86,7 +86,7 @@ describe("Announce movement test", ()=> { }); test("Feature index content is correct on moveend", async () => { - await page.keyboard.press("ArrowUp"); + await page.keyboard.press("ArrowRight"); await page.waitForTimeout(1000); const spanCount = await page.$eval( "div > output.mapml-feature-index > span", @@ -97,7 +97,7 @@ describe("Announce movement test", ()=> { (span) => span.innerText ); - await expect(spanCount).toEqual(1); + await expect(spanCount).toEqual(2); await expect(firstFeature).toEqual("1 Maine"); }); From bedc2ae15d4d4c5e0e67a4aaccdd0442ff8fc3ad Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Wed, 18 May 2022 23:07:53 -0400 Subject: [PATCH 26/31] Fix index popup issue + make sure index keys are valid onKeyDown --- src/mapml/layers/FeatureIndexOverlay.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index cb7c11057..67159af28 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -13,7 +13,7 @@ export var FeatureIndexOverlay = L.Layer.extend({ this._body.index = 0; map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this); - map.on('moveend focus', this._checkOverlap, this); + map.on('moveend focus layeradd', this._checkOverlap, this); map.on("keydown", this._onKeyDown, this); this._addOrRemoveFeatureIndex(); }, @@ -107,7 +107,9 @@ export var FeatureIndexOverlay = L.Layer.extend({ let body = this._body; let key = e.originalEvent.keyCode; if (key >= 49 && key <= 55){ + if(!body.allFeatures[body.index]) return; let feature = body.allFeatures[body.index][key - 49]; + if (!feature) return; let layer = feature.layer; if (layer) { this._map.featureIndex.currentIndex = feature.index - 1; From 621f15f32a570eb9b74f22009ee9919a3a3a0d36 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Thu, 19 May 2022 12:15:34 -0400 Subject: [PATCH 27/31] Fix tabbing issue + add popup test --- src/mapml/layers/FeatureIndexOverlay.js | 2 +- src/mapml/layers/TemplatedFeaturesLayer.js | 1 + test/e2e/core/featureIndexOverlay.html | 21 +++++++++++++++++++- test/e2e/core/featureIndexOverlay.test.js | 23 ++++++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index 67159af28..3b88f8fe6 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -13,7 +13,7 @@ export var FeatureIndexOverlay = L.Layer.extend({ this._body.index = 0; map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this); - map.on('moveend focus layeradd', this._checkOverlap, this); + map.on('moveend focus templatedfeatureslayeradd', this._checkOverlap, this); map.on("keydown", this._onKeyDown, this); this._addOrRemoveFeatureIndex(); }, diff --git a/src/mapml/layers/TemplatedFeaturesLayer.js b/src/mapml/layers/TemplatedFeaturesLayer.js index f0ea8e24e..d6d0b96f2 100644 --- a/src/mapml/layers/TemplatedFeaturesLayer.js +++ b/src/mapml/layers/TemplatedFeaturesLayer.js @@ -117,6 +117,7 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ _pullFeatureFeed(this._getfeaturesUrl(), MAX_PAGES) .then(function() { map.addLayer(features); + map.fire("templatedfeatureslayeradd"); M.TemplatedFeaturesLayer.prototype._updateTabIndex(context); }) .catch(function (error) { console.log(error);}); diff --git a/test/e2e/core/featureIndexOverlay.html b/test/e2e/core/featureIndexOverlay.html index 5820dd875..417f9ab87 100644 --- a/test/e2e/core/featureIndexOverlay.html +++ b/test/e2e/core/featureIndexOverlay.html @@ -14,10 +14,29 @@ - + + + + + + + restaurants + italian + african + asian + cajun + mexican + indian + + + + + + \ No newline at end of file diff --git a/test/e2e/core/featureIndexOverlay.test.js b/test/e2e/core/featureIndexOverlay.test.js index 8b855e21d..ae9de150a 100644 --- a/test/e2e/core/featureIndexOverlay.test.js +++ b/test/e2e/core/featureIndexOverlay.test.js @@ -118,4 +118,27 @@ describe("Feature Index Overlay test", ()=> { await expect(reticle).toEqual(false); }); + test("Feature index", async () => { + await page.mouse.click(10, 600); + await page.waitForTimeout(500); + await page.focus('#map2 > div'); + await page.waitForTimeout(500); + + for(let i = 0; i < 2; i++){ + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(1000); + } + + await page.keyboard.press("1"); + await page.waitForTimeout(500); + + const hasPopup = await page.$eval( + "#map2 > div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane", + (popup) => popup.hasChildNodes() + ); + + await expect(hasPopup).toEqual(true) + + }) + }); \ No newline at end of file From 36ba188ff222e92464c23358d5061359371f444c Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Thu, 19 May 2022 19:38:31 -0400 Subject: [PATCH 28/31] Change popup behaviour --- src/mapml/layers/FeatureIndexOverlay.js | 10 +++++- test/e2e/core/featureIndexOverlay.test.js | 44 ++++++++++++++++++----- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index 3b88f8fe6..f023c579d 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -113,7 +113,10 @@ export var FeatureIndexOverlay = L.Layer.extend({ let layer = feature.layer; if (layer) { this._map.featureIndex.currentIndex = feature.index - 1; - if (layer._popup) layer.openPopup(); + if (layer._popup){ + this._map.closePopup(); + layer.openPopup(); + } else layer.options.group.focus(); } } else if(key === 56){ @@ -152,6 +155,11 @@ export var FeatureIndexOverlay = L.Layer.extend({ if (e && e.type === "focus") { obj._container.removeAttribute("hidden"); if (features !== 0) obj._output.classList.remove("mapml-screen-reader-output"); + } else if (e && e.originalEvent && e.originalEvent.type === 'pointermove') { + obj._container.setAttribute("hidden", ""); + obj._output.classList.add("mapml-screen-reader-output"); + } else if (e && e.target._popup) { + } else if (e && e.type === "blur") { obj._container.setAttribute("hidden", ""); obj._output.classList.add("mapml-screen-reader-output"); diff --git a/test/e2e/core/featureIndexOverlay.test.js b/test/e2e/core/featureIndexOverlay.test.js index ae9de150a..abb7e0fb5 100644 --- a/test/e2e/core/featureIndexOverlay.test.js +++ b/test/e2e/core/featureIndexOverlay.test.js @@ -118,27 +118,53 @@ describe("Feature Index Overlay test", ()=> { await expect(reticle).toEqual(false); }); - test("Feature index", async () => { + test("Popup test with templated features", async () => { await page.mouse.click(10, 600); await page.waitForTimeout(500); await page.focus('#map2 > div'); await page.waitForTimeout(500); - for(let i = 0; i < 2; i++){ - await page.keyboard.press("ArrowLeft"); - await page.waitForTimeout(1000); - } + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(1000); + await page.keyboard.press("Control+ArrowUp"); + await page.waitForTimeout(1000); await page.keyboard.press("1"); await page.waitForTimeout(500); - const hasPopup = await page.$eval( + const popupCount = await page.$eval( + "#map2 > div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane", + (popup) => popup.childElementCount + ); + const popupName = await page.$eval( "#map2 > div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane", - (popup) => popup.hasChildNodes() + (popup) => popup.children[0].innerText ); - await expect(hasPopup).toEqual(true) + await expect(popupCount).toEqual(1); + await expect(popupName).toContain("Hareg Cafe & Variety"); + }); + + test("Opening another popup with index keys closes already open popup", async () => { + await page.keyboard.press("2"); + await page.waitForTimeout(500); - }) + const popupCount = await page.$eval( + "#map2 > div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane", + (popup) => popup.childElementCount + ); + const popupName = await page.$eval( + "#map2 > div > div.leaflet-pane.leaflet-map-pane > div.leaflet-pane.leaflet-popup-pane", + (popup) => popup.children[0].innerText + ); + const overlay = await page.$eval( + "#map2 > div > output.mapml-feature-index", + (output) => output.classList.contains("mapml-screen-reader-output") + ); + + await expect(popupCount).toEqual(1); + await expect(popupName).toContain("Banditos"); + await expect(overlay).toEqual(false); + }); }); \ No newline at end of file From 215b4c04ce1ffead5ba4e9fa7c7bd904ea3cbcc5 Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Fri, 20 May 2022 14:00:47 -0400 Subject: [PATCH 29/31] Remove features from tabindex when FeatureIndexOverlay is enabled --- src/mapml/handlers/FeatureIndex.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mapml/handlers/FeatureIndex.js b/src/mapml/handlers/FeatureIndex.js index 0011fd51d..6682bee84 100644 --- a/src/mapml/handlers/FeatureIndex.js +++ b/src/mapml/handlers/FeatureIndex.js @@ -90,8 +90,7 @@ export var FeatureIndex = L.Handler.extend({ b.dist = Math.sqrt(Math.pow(bc.x - mc.x, 2) + Math.pow(bc.y - mc.y, 2)); return a.dist - b.dist; }); - - this.inBoundFeatures[0].path.setAttribute("tabindex", 0); + if(!M.options.featureIndexOverlayOption) this.inBoundFeatures[0].path.setAttribute("tabindex", 0); }, /** From 12a6616fbd0ddd08788277ced935befd77614103 Mon Sep 17 00:00:00 2001 From: ben-lu-uw <69722289+ben-lu-uw@users.noreply.github.com> Date: Thu, 26 May 2022 17:59:00 -0400 Subject: [PATCH 30/31] Update feature index screen reading behavior (#2) * Only start checking overlap when/after the first focus happens * Announce map details and then feature index on initial focus * Announce map details and then feature index on refocus * Focus map directly when popup is closed and feature index option is on * Remove reticle when the map is not focused * Add hidden comma for brief pauses in output reading * Announce feature index on popupclose * Update featureIndexOverlay.test.js --- src/mapml.css | 10 +++ src/mapml/features/featureGroup.js | 8 +-- src/mapml/layers/FeatureIndexOverlay.js | 83 +++++++++++++++-------- src/mapml/layers/MapLayer.js | 6 +- test/e2e/core/featureIndexOverlay.test.js | 10 +-- 5 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index 241e12e8b..1610a4ea6 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -867,3 +867,13 @@ label.mapml-layer-item-toggle { border-radius: 4px; } +.mapml-feature-index-content > span > span{ + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: pre; + width: 1px; +} + diff --git a/src/mapml/features/featureGroup.js b/src/mapml/features/featureGroup.js index 1ec50019d..12122cf0a 100644 --- a/src/mapml/features/featureGroup.js +++ b/src/mapml/features/featureGroup.js @@ -59,7 +59,7 @@ export var FeatureGroup = L.FeatureGroup.extend({ * @private */ _handleFocus: function(e) { - if((e.keyCode === 9 || e.keyCode === 16) && e.type === "keydown"){ + if((e.keyCode === 9 || e.keyCode === 16 || e.keyCode === 27) && e.type === "keydown"){ let index = this._map.featureIndex.currentIndex; if(e.keyCode === 9 && e.shiftKey) { if(index === this._map.featureIndex.inBoundFeatures.length - 1) @@ -78,13 +78,13 @@ export var FeatureGroup = L.FeatureGroup.extend({ this._map.featureIndex.inBoundFeatures[0].path.setAttribute("tabindex", -1); this._map.featureIndex.inBoundFeatures[index].path.setAttribute("tabindex", 0); } + } else if(e.keyCode === 27 && this._map.options.mapEl.shadowRoot.activeElement.nodeName === "g"){ + this._map.featureIndex.currentIndex = 0; + this._map._container.focus(); } } else if (!([9, 16, 13, 27, 49, 50, 51, 52, 53, 54, 55].includes(e.keyCode))){ this._map.featureIndex.currentIndex = 0; this._map.featureIndex.inBoundFeatures[0].path.focus(); - } else if(e.keyCode === 27){ - this._map.featureIndex.currentIndex = 0; - this._map._container.focus(); } if(e.target.tagName.toUpperCase() !== "G") return; diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index f023c579d..4b79be412 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -11,15 +11,14 @@ export var FeatureIndexOverlay = L.Layer.extend({ this._output.setAttribute("aria-atomic", "true"); this._body = L.DomUtil.create("span", "mapml-feature-index-content", this._output); this._body.index = 0; - + this._output.initialFocus = false; map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this); map.on('moveend focus templatedfeatureslayeradd', this._checkOverlap, this); map.on("keydown", this._onKeyDown, this); this._addOrRemoveFeatureIndex(); }, - _checkOverlap: function () { - this._map.fire("mapkeyboardfocused"); + _calculateReticleBounds: function () { let bounds = this._map.getPixelBounds(); let center = bounds.getCenter(); let wRatio = Math.abs(bounds.min.x - bounds.max.x) / (this._map.options.mapEl.width); @@ -34,8 +33,20 @@ export var FeatureIndexOverlay = L.Layer.extend({ let minPoint = L.point(center.x - w, center.y + h); let maxPoint = L.point(center.x + w, center.y - h); let b = L.bounds(minPoint, maxPoint); - let featureIndexBounds = M.pixelToPCRSBounds(b,this._map.getZoom(),this._map.options.projection); + return M.pixelToPCRSBounds(b,this._map.getZoom(),this._map.options.projection); + }, + _checkOverlap: function (e) { + if(e.type === "focus") this._output.initialFocus = true; + if(!this._output.initialFocus) return; + if(this._output.popupClosed) { + this._output.popupClosed = false; + return; + } + + this._map.fire("mapkeyboardfocused"); + + let featureIndexBounds = this._calculateReticleBounds(); let features = this._map.featureIndex.inBoundFeatures; let index = 1; let keys = Object.keys(features); @@ -83,7 +94,9 @@ export var FeatureIndexOverlay = L.Layer.extend({ _updateOutput: function (label, index, key) { let span = document.createElement("span"); span.setAttribute("data-index", index); - span.innerHTML = `${key}` + " " + label; + //", " adds a brief auditory pause when a screen reader is reading through the feature index + //also prevents names with numbers + key from being combined when read + span.innerHTML = `${key}` + " " + label + ", "; return span; }, @@ -144,37 +157,49 @@ export var FeatureIndexOverlay = L.Layer.extend({ }, _toggleEvents: function (){ - this._map.on("viewreset move moveend focus blur", this._addOrRemoveFeatureIndex, this); + this._map.on("viewreset move moveend focus blur popupclose", this._addOrRemoveFeatureIndex, this); }, _addOrRemoveFeatureIndex: function (e) { - let obj = this; let features = this._body.allFeatures ? this._body.allFeatures.length : 0; - setTimeout(function() { - if (e && e.type === "focus") { - obj._container.removeAttribute("hidden"); - if (features !== 0) obj._output.classList.remove("mapml-screen-reader-output"); - } else if (e && e.originalEvent && e.originalEvent.type === 'pointermove') { - obj._container.setAttribute("hidden", ""); - obj._output.classList.add("mapml-screen-reader-output"); - } else if (e && e.target._popup) { - - } else if (e && e.type === "blur") { - obj._container.setAttribute("hidden", ""); - obj._output.classList.add("mapml-screen-reader-output"); - } else if (obj._map.isFocused) { - obj._container.removeAttribute("hidden"); - if (features !== 0) { - obj._output.classList.remove("mapml-screen-reader-output"); - } else { - obj._output.classList.add("mapml-screen-reader-output"); - } + //Toggle aria-hidden attribute so screen reader rereads the feature index on focus + if (!this._output.initialFocus) { + this._output.setAttribute("aria-hidden", "true"); + } else if(this._output.hasAttribute("aria-hidden")){ + let obj = this; + setTimeout(function () { + obj._output.removeAttribute("aria-hidden"); + }, 100); + } + + if(e && e.type === "popupclose") { + this._output.setAttribute("aria-hidden", "true"); + this._output.popupClosed = true; + } else if (e && e.type === "focus") { + this._container.removeAttribute("hidden"); + if (features !== 0) this._output.classList.remove("mapml-screen-reader-output"); + } else if (e && e.originalEvent && e.originalEvent.type === 'pointermove') { + this._container.setAttribute("hidden", ""); + this._output.classList.add("mapml-screen-reader-output"); + } else if (e && e.target._popup) { + + } else if (e && e.type === "blur") { + this._container.setAttribute("hidden", ""); + this._output.classList.add("mapml-screen-reader-output"); + this._output.initialFocus = false; + this._addOrRemoveFeatureIndex(); + } else if (this._map.isFocused && e) { + this._container.removeAttribute("hidden"); + if (features !== 0) { + this._output.classList.remove("mapml-screen-reader-output"); } else { - obj._container.setAttribute("hidden", ""); - obj._output.classList.add("mapml-screen-reader-output"); + this._output.classList.add("mapml-screen-reader-output"); } - }, 0); + } else { + this._container.setAttribute("hidden", ""); + this._output.classList.add("mapml-screen-reader-output"); + } }, diff --git a/src/mapml/layers/MapLayer.js b/src/mapml/layers/MapLayer.js index 3ffb63cb4..bc078d8a1 100644 --- a/src/mapml/layers/MapLayer.js +++ b/src/mapml/layers/MapLayer.js @@ -1314,7 +1314,7 @@ export var MapMLLayer = L.Layer.extend({ content.focus(); - if(group) { + if(group && !M.options.featureIndexOverlayOption) { // e.target = this._map // Looks for keydown, more specifically tab and shift tab group.setAttribute("aria-expanded", "true"); @@ -1349,15 +1349,15 @@ export var MapMLLayer = L.Layer.extend({ if((focusEvent.originalEvent.keyCode === 13 && path[0].classList.contains("leaflet-popup-close-button")) || focusEvent.originalEvent.keyCode === 27 ){ L.DomEvent.stopPropagation(focusEvent); - map._container.focus(); map.closePopup(popup); + map._container.focus(); if(focusEvent.originalEvent.keyCode !== 27)map._popupClosed = true; } else if (isTab && path[0].classList.contains("leaflet-popup-close-button")){ map.closePopup(popup); } else if ((path[0].title==="Focus Map" || path[0].classList.contains("mapml-popup-content")) && isTab && shiftPressed){ + map.closePopup(popup); 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); } diff --git a/test/e2e/core/featureIndexOverlay.test.js b/test/e2e/core/featureIndexOverlay.test.js index abb7e0fb5..d0e04cb12 100644 --- a/test/e2e/core/featureIndexOverlay.test.js +++ b/test/e2e/core/featureIndexOverlay.test.js @@ -49,8 +49,8 @@ describe("Feature Index Overlay test", ()=> { ); await expect(spanCount).toEqual(8); - await expect(firstFeature).toEqual("1 Vermont"); - await expect(moreResults).toEqual("9 More results"); + await expect(firstFeature).toContain("1 Vermont"); + await expect(moreResults).toContain("9 More results"); }); test("Feature index more results are correct", async () => { @@ -71,8 +71,8 @@ describe("Feature Index Overlay test", ()=> { ); await expect(spanCount).toEqual(5); - await expect(firstFeature).toEqual("1 Pennsylvania"); - await expect(prevResults).toEqual("8 Previous results"); + await expect(firstFeature).toContain("1 Pennsylvania"); + await expect(prevResults).toContain("8 Previous results"); }); test("Feature index previous results are correct", async () => { @@ -98,7 +98,7 @@ describe("Feature Index Overlay test", ()=> { ); await expect(spanCount).toEqual(2); - await expect(firstFeature).toEqual("1 Maine"); + await expect(firstFeature).toContain("1 Maine"); }); test("Feature index overlay is hidden when empty, reticle still visible", async () => { From b0e233f71b4e1bdd4d5be6da843a44566e80618f Mon Sep 17 00:00:00 2001 From: ben-lu-uw Date: Fri, 27 May 2022 10:01:55 -0400 Subject: [PATCH 31/31] Hide reticle when popup is open --- src/mapml/layers/FeatureIndexOverlay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mapml/layers/FeatureIndexOverlay.js b/src/mapml/layers/FeatureIndexOverlay.js index 4b79be412..9b48213b5 100644 --- a/src/mapml/layers/FeatureIndexOverlay.js +++ b/src/mapml/layers/FeatureIndexOverlay.js @@ -183,7 +183,7 @@ export var FeatureIndexOverlay = L.Layer.extend({ this._container.setAttribute("hidden", ""); this._output.classList.add("mapml-screen-reader-output"); } else if (e && e.target._popup) { - + this._container.setAttribute("hidden", ""); } else if (e && e.type === "blur") { this._container.setAttribute("hidden", ""); this._output.classList.add("mapml-screen-reader-output");