From 831e5fb94e79112e7a1b7eebd63e93c1af414705 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Wed, 27 Jan 2021 10:03:20 -0500 Subject: [PATCH 01/16] Allow features to be focused --- src/mapml/layers/FeatureLayer.js | 14 ++++++++++++++ src/mapml/layers/TemplatedFeaturesLayer.js | 9 +++++++++ 2 files changed, 23 insertions(+) diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index 0083cc18e..b1e04ddad 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -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 { @@ -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){ @@ -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 diff --git a/src/mapml/layers/TemplatedFeaturesLayer.js b/src/mapml/layers/TemplatedFeaturesLayer.js index 5fee6ed43..b198c1e6a 100644 --- a/src/mapml/layers/TemplatedFeaturesLayer.js +++ b/src/mapml/layers/TemplatedFeaturesLayer.js @@ -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}) @@ -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);}); }, @@ -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; From ba5078bea886b1c9e96790dd8ba04522372d9244 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Thu, 28 Jan 2021 15:32:01 -0500 Subject: [PATCH 02/16] Adds crosshair, shows on keyboard movement --- src/mapml-viewer.js | 1 + src/mapml.css | 16 ++++++++ src/mapml/handlers/ContextMenu.js | 2 +- src/mapml/index.js | 4 ++ src/mapml/layers/Crosshair.js | 68 +++++++++++++++++++++++++++++++ src/web-map.js | 1 + 6 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/mapml/layers/Crosshair.js diff --git a/src/mapml-viewer.js b/src/mapml-viewer.js index 397413ea8..dc22fb5b5 100644 --- a/src/mapml-viewer.js +++ b/src/mapml-viewer.js @@ -211,6 +211,7 @@ export class MapViewer extends HTMLElement { this._attributionControl = this._map.attributionControl.setPrefix('Maps4HTML | Leaflet'); 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). diff --git a/src/mapml.css b/src/mapml.css index ed66167ec..964e28a5d 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -374,3 +374,19 @@ summary { .leaflet-container .leaflet-control-container { visibility: unset!important; } + +.mapml-crosshair-active { + margin: -18px 0 0 -18px; + width: 36px; + height: 36px; + left: 50%; + top: 50%; + content: ''; + display: block; + position: absolute; + z-index: 10000; +} + +.mapml-crosshair-hidden * { + visibility: hidden; +} \ No newline at end of file diff --git a/src/mapml/handlers/ContextMenu.js b/src/mapml/handlers/ContextMenu.js index ad64e3f39..dd77210b6 100644 --- a/src/mapml/handlers/ContextMenu.js +++ b/src/mapml/handlers/ContextMenu.js @@ -151,7 +151,7 @@ export var ContextMenu = L.Handler.extend({ L.DomEvent .on(container, 'mouseleave', this._hide, this) - .on(document, 'keydown', this._onKeyDown, this); + .on(container, 'keydown', this._onKeyDown, this); if (L.Browser.touch) { L.DomEvent.on(document, this._touchstart, this._hide, this); diff --git a/src/mapml/index.js b/src/mapml/index.js index 68c7d2ffb..308d91e08 100644 --- a/src/mapml/index.js +++ b/src/mapml/index.js @@ -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) { @@ -625,4 +626,7 @@ M.mapMLStaticTileLayer = mapMLStaticTileLayer; M.DebugOverlay = DebugOverlay; M.debugOverlay = debugOverlay; +M.Crosshair = Crosshair; +M.crosshair = crosshair; + }(window, document)); diff --git a/src/mapml/layers/Crosshair.js b/src/mapml/layers/Crosshair.js new file mode 100644 index 000000000..be9402edb --- /dev/null +++ b/src/mapml/layers/Crosshair.js @@ -0,0 +1,68 @@ +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 = ` + + `; + + this._container = L.DomUtil.create("div", "mapml-crosshair-hidden", map._container); + this._container.innerHTML = svgInnerHTML; + this._mapFocused = false; + + map.on("viewreset move moveend layerchange layeradd layerremove overlayremove", this._addOrRemoveCrosshair, this); + L.DomEvent.on(map._container, "keyup mousedown", this._isMapFocused, this); + + this._addOrRemoveCrosshair(); + }, + + _addOrRemoveCrosshair: function (e) { + let layers = this._map.options.mapEl.layers; + if (this._mapFocused) { + for (let layer of layers) { + if (layer.checked && layer._layer.queryable) { + this._container.className = "mapml-crosshair-active"; + return; + } + } + } + this._container.className = "mapml-crosshair-hidden"; + }, + + _isMapFocused: function (e) { + if (e.type === "keyup" && e.target.classList.contains("leaflet-container") && (+e.keyCode >= 37 && +e.keyCode <= 40 || +e.keyCode === 32)) { + this._mapFocused = true; + } else { + this._mapFocused = false; + } + this._addOrRemoveCrosshair(); + }, + +}); + + +export var crosshair = function (options) { + return new Crosshair(options); +}; \ No newline at end of file diff --git a/src/web-map.js b/src/web-map.js index b48a09796..0e9c414b3 100644 --- a/src/web-map.js +++ b/src/web-map.js @@ -224,6 +224,7 @@ export class WebMap extends HTMLMapElement { this._attributionControl = this._map.attributionControl.setPrefix('Maps4HTML | Leaflet'); this.setControls(false,false,true); + this._crosshair = M.crosshair().addTo(this._map); if (this.hasAttribute('name')) { var name = this.getAttribute('name'); if (name) { From 1c75af63a627f8ec14982cf1a5fe3967391f82b6 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Thu, 28 Jan 2021 15:53:04 -0500 Subject: [PATCH 03/16] Shows crosshair on tab --- src/mapml.css | 6 +----- src/mapml/layers/Crosshair.js | 12 +++++++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index 964e28a5d..2f066f45c 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -375,7 +375,7 @@ summary { visibility: unset!important; } -.mapml-crosshair-active { +.mapml-crosshair { margin: -18px 0 0 -18px; width: 36px; height: 36px; @@ -385,8 +385,4 @@ summary { display: block; position: absolute; z-index: 10000; -} - -.mapml-crosshair-hidden * { - visibility: hidden; } \ No newline at end of file diff --git a/src/mapml/layers/Crosshair.js b/src/mapml/layers/Crosshair.js index be9402edb..a3cc850e9 100644 --- a/src/mapml/layers/Crosshair.js +++ b/src/mapml/layers/Crosshair.js @@ -28,12 +28,12 @@ export var Crosshair = L.Layer.extend({ 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" /> `; - this._container = L.DomUtil.create("div", "mapml-crosshair-hidden", map._container); + this._container = L.DomUtil.create("div", "mapml-crosshair", map._container); this._container.innerHTML = svgInnerHTML; this._mapFocused = false; map.on("viewreset move moveend layerchange layeradd layerremove overlayremove", this._addOrRemoveCrosshair, this); - L.DomEvent.on(map._container, "keyup mousedown", this._isMapFocused, this); + L.DomEvent.on(map._container, "keydown keyup mousedown", this._isMapFocused, this); this._addOrRemoveCrosshair(); }, @@ -43,16 +43,18 @@ export var Crosshair = L.Layer.extend({ if (this._mapFocused) { for (let layer of layers) { if (layer.checked && layer._layer.queryable) { - this._container.className = "mapml-crosshair-active"; + this._container.style.visibility = null; return; } } } - this._container.className = "mapml-crosshair-hidden"; + this._container.style.visibility = "hidden"; }, _isMapFocused: function (e) { - if (e.type === "keyup" && e.target.classList.contains("leaflet-container") && (+e.keyCode >= 37 && +e.keyCode <= 40 || +e.keyCode === 32)) { + if (["keydown", "keyup"].includes(e.type) && e.target.classList.contains("leaflet-container") && (+e.keyCode >= 37 && +e.keyCode <= 40 || +e.keyCode === 32)) { + this._mapFocused = true; + } else if (e.type === "keyup" && e.target.classList.contains("leaflet-container") && +e.keyCode === 9) { this._mapFocused = true; } else { this._mapFocused = false; From 8d82998b4101ac37d2348b057a9d9c061f2cad98 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Thu, 28 Jan 2021 16:25:59 -0500 Subject: [PATCH 04/16] Only run on moveend if there are queryable layers --- src/mapml/layers/Crosshair.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/mapml/layers/Crosshair.js b/src/mapml/layers/Crosshair.js index a3cc850e9..170e3306c 100644 --- a/src/mapml/layers/Crosshair.js +++ b/src/mapml/layers/Crosshair.js @@ -31,28 +31,46 @@ export var Crosshair = L.Layer.extend({ this._container = L.DomUtil.create("div", "mapml-crosshair", map._container); this._container.innerHTML = svgInnerHTML; this._mapFocused = false; + this._isQueryable = false; - map.on("viewreset move moveend layerchange layeradd layerremove overlayremove", this._addOrRemoveCrosshair, this); + 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) { - this._container.style.visibility = null; - return; + return true; } } } - this._container.style.visibility = "hidden"; + return false; }, _isMapFocused: function (e) { - if (["keydown", "keyup"].includes(e.type) && e.target.classList.contains("leaflet-container") && (+e.keyCode >= 37 && +e.keyCode <= 40 || +e.keyCode === 32)) { + 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; From 9d9029ecbca2fb7ce4ca32b6e0c21287c3cfa6db Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Fri, 29 Jan 2021 10:17:49 -0500 Subject: [PATCH 05/16] Revert listener change --- src/mapml/handlers/ContextMenu.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mapml/handlers/ContextMenu.js b/src/mapml/handlers/ContextMenu.js index dd77210b6..ad64e3f39 100644 --- a/src/mapml/handlers/ContextMenu.js +++ b/src/mapml/handlers/ContextMenu.js @@ -151,7 +151,7 @@ export var ContextMenu = L.Handler.extend({ L.DomEvent .on(container, 'mouseleave', this._hide, this) - .on(container, 'keydown', this._onKeyDown, this); + .on(document, 'keydown', this._onKeyDown, this); if (L.Browser.touch) { L.DomEvent.on(document, this._touchstart, this._hide, this); From 97b811dbd41a09ab979ead43332947bbf269c8cb Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Fri, 29 Jan 2021 12:57:50 -0500 Subject: [PATCH 06/16] Adds tests for keyboard interaction --- test/e2e/core/keyboardInteraction.html | 85 ++++++++++++++ test/e2e/core/keyboardInteraction.test.js | 132 ++++++++++++++++++++++ test/e2e/core/layerContextMenu.test.js | 16 --- 3 files changed, 217 insertions(+), 16 deletions(-) create mode 100644 test/e2e/core/keyboardInteraction.html create mode 100644 test/e2e/core/keyboardInteraction.test.js diff --git a/test/e2e/core/keyboardInteraction.html b/test/e2e/core/keyboardInteraction.html new file mode 100644 index 000000000..0c46ea364 --- /dev/null +++ b/test/e2e/core/keyboardInteraction.html @@ -0,0 +1,85 @@ + + + + Keyboard Interaction Test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Test

+
+ + + 11 11 12 11 12 12 11 12 + + +
+ + + +

Test

+
+ + + 257421 -3567196 -271745 1221771 -3896544 242811 -3183549 -2613313 + + +
+ + + +

Test

+
+ + + 2771 3106 2946 3113 2954 3210 2815 3192 + + +
+
+ +
+ + + \ No newline at end of file diff --git a/test/e2e/core/keyboardInteraction.test.js b/test/e2e/core/keyboardInteraction.test.js new file mode 100644 index 000000000..de51ab648 --- /dev/null +++ b/test/e2e/core/keyboardInteraction.test.js @@ -0,0 +1,132 @@ +const playwright = require("playwright"); +jest.setTimeout(50000); +(async () => { + for (const browserType of BROWSER) { + describe( + "Playwright Keyboard Navigation + Query Layer Tests in " + browserType, + () => { + beforeAll(async () => { + browser = await playwright[browserType].launch({ + headless: ISHEADLESS, + slowMo: 50, + }); + context = await browser.newContext(); + page = await context.newPage(); + if (browserType === "firefox") { + await page.waitForNavigation(); + } + await page.goto(PATH + "keyboardInteraction.html"); + }); + + afterAll(async function () { + await browser.close(); + }); + describe("Crosshair Tests in " + browserType, () => { + test("[" + browserType + "]" + " Crosshair hidden onload, shows on focus", async () => { + const beforeTabHidden = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + await page.keyboard.press("Tab"); + const afterTab = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + expect(beforeTabHidden).toEqual("hidden"); + expect(afterTab).toEqual(""); + }); + + test("[" + browserType + "]" + " Crosshair remains on map move with arrow keys + space", async () => { + await page.keyboard.press("ArrowUp"); + await page.waitForTimeout(500); + await page.keyboard.press("ArrowDown"); + await page.waitForTimeout(500); + await page.keyboard.press("ArrowLeft"); + await page.waitForTimeout(500); + await page.keyboard.press("ArrowRight"); + await page.waitForTimeout(500); + await page.keyboard.press(" ") + await page.waitForTimeout(500); + const afterMove = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + expect(afterMove).toEqual(""); + }); + + test("[" + browserType + "]" + " Crosshair hidden on esc + tab out", async () => { + await page.keyboard.press("Escape"); + const afterEsc = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + await page.keyboard.press("ArrowUp"); + + await page.keyboard.press("Tab"); + const afterTab = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + + expect(afterEsc).toEqual("hidden"); + expect(afterTab).toEqual("hidden"); + }); + + test("[" + browserType + "]" + " Crosshair hidden when queryable layer is unselected, shows on reselect", async () => { + await page.click("body > mapml-viewer"); + await page.keyboard.press("ArrowUp"); + await page.evaluateHandle(() => document.querySelector("layer-").removeAttribute("checked")); + const afterUncheck = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + + await page.evaluateHandle(() => document.querySelector("layer-").setAttribute("checked", "")); + const afterCheck = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + + expect(afterUncheck).toEqual("hidden"); + expect(afterCheck).toEqual(""); + }); + }); + describe("Tab Navigable Tests in " + browserType, () => { + function findFocusedNode(node) { + if (node.focused) + return node; + for (const child of node.children || []) { + const foundNode = findFocusedNode(child); + return foundNode; + } + return null; + } + test("[" + browserType + "]" + " Tab focuses inline features", async () => { + await page.click("div > div.leaflet-control-container > div.leaflet-top.leaflet-left > div.mapml-reload-button.leaflet-bar.leaflet-control > a"); + await page.waitForTimeout(1000); + await page.click("body > mapml-viewer"); + await page.keyboard.press("Escape"); + + await page.keyboard.press("Tab"); + const aHandle = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nextHandle = await page.evaluateHandle(doc => doc.shadowRoot, aHandle); + const resultHandle = await page.evaluateHandle(root => root.activeElement, nextHandle); + const focused = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandle)).jsonValue(); + + await page.keyboard.press("Tab"); + const aHandleNext = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nextHandleNext = await page.evaluateHandle(doc => doc.shadowRoot, aHandleNext); + const resultHandleNext = await page.evaluateHandle(root => root.activeElement, nextHandleNext); + const focusedNext = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandleNext)).jsonValue(); + + expect(focused).toEqual("M330 83L553 83L553 339L330 339z"); + expect(focusedNext).toEqual("M-53 451L153 508L113 146L-53 191z"); + }); + + test("[" + browserType + "]" + " Tab focuses fetched features", async () => { + await page.evaluateHandle(() => document.getElementById("vector").setAttribute("checked", "")); + await page.click("body > mapml-viewer"); + await page.keyboard.press("Escape"); + + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + const aHandle = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nextHandle = await page.evaluateHandle(doc => doc.shadowRoot, aHandle); + const resultHandle = await page.evaluateHandle(root => root.activeElement, nextHandle); + const focused = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandle)).jsonValue(); + + await page.keyboard.press("Tab"); + const aHandleNext = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nextHandleNext = await page.evaluateHandle(doc => doc.shadowRoot, aHandleNext); + const resultHandleNext = await page.evaluateHandle(root => root.activeElement, nextHandleNext); + const focusedNext = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandleNext)).jsonValue(); + + expect(focused).toEqual("M190 357L203 355L209 374L213 379L211 380L213 391L193 393L193 395L195 396L194 400L191 399L191 397L189 398L189 400L187 400L185 386L184 357z"); + expect(focusedNext).toEqual("M-30 139L-29 138L-31 140zM-30 136L-29 138L-31 138zM-29 126L-28 127L-30 127zM-32 125L-30 131L-32 137L-31 138L-34 141L-34 138L-36 136L-36 139L-37 136L-34 132L-35 131L-33 126zM-36 130L-35 131L-37 132zM-31 121L-30 120L-28 122L-28 127L-31 125L-31 121zM-33 123L-34 122L-32 120L-31 124L-32 123L-35 126L-35 124zM-36 123L-37 124L-37 118L-35 113L-32 114L-34 120zM-27 110L-26 115L-27 114L-28 118L-32 119L-33 118L-30 115L-27 108zM-36 113L-36 114zM-33 106L-29 110L-31 112L-31 115L-33 111L-34 113L-35 112L-35 108L-33 105zM3 6L7 13L11 15L13 23L-37 80L-35 84L-31 85L-33 86L-33 99L-29 99L-27 97L-22 98L-22 117L-24 128L-20 136L-23 142L-28 144L-29 143L-26 140L-29 139L-30 136L-28 135L-30 136L-30 134L-28 132L-30 132L-26 127L-28 120L-26 117L-27 107L-25 102L-29 109L-29 103L-31 100L-31 106L-35 103L-36 92L-38 89L-34 86L-38 86L-40 84L-43 77L-48 74L-48 69L-50 69L-50 66L-53 64L-50 65L-50 60L-53 58L-53 -20L-49 -17L-53 -14L-47 -7L-48 -10L-45 -14L-44 -13L-47 -9L-43 -12L-45 -18L-42 -20L-40 -31L-36 -31L-33 -26L-30 -25L-19 -26L-20 -25L-18 -23L-13 -23L-12 -20L-5 -18L-4 -15L-7 -14L-5 -14L-5 -11L-1 -7L-3 -6L-2 -2L2 1L3 5z"); + }); + }); + } + ); + } +})(); \ No newline at end of file diff --git a/test/e2e/core/layerContextMenu.test.js b/test/e2e/core/layerContextMenu.test.js index 43810b7d7..3b8ba493c 100644 --- a/test/e2e/core/layerContextMenu.test.js +++ b/test/e2e/core/layerContextMenu.test.js @@ -1,22 +1,6 @@ const playwright = require("playwright"); jest.setTimeout(50000); (async () => { - - //expected topLeft values in the different cs, at the different - //positions the map goes in - let expectedPCRS = [ - { horizontal: -5537023.0124460235, vertical: 2671749.64016594 }, - { horizontal: -2810486.309372615, vertical: 5328171.619676568 }]; - let expectedGCRS = [ - { horizontal: -134.50882532096858, vertical: 34.758856143866666 }, - { horizontal: -146.23778791492126, vertical: 54.997129539321016 }]; - let expectedFirstTileMatrix = [ - { horizontal: 2.96484375, vertical: 3.7304687500000004 }, - { horizontal: 3.242456896551724, vertical: 3.4599946120689657 }]; - let expectedFirstTCRS = [ - { horizontal: 759, vertical: 955.0000000000001 }, - { horizontal: 830.0689655172414, vertical: 885.7586206896552 }]; - for (const browserType of BROWSER) { describe( "Playwright Layer Context Menu Tests in " + browserType, From f5911b0fb29778b1615669824507cc753271b6ab Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Tue, 2 Feb 2021 09:28:25 -0500 Subject: [PATCH 07/16] Remove close button from feature popup --- src/mapml/layers/MapLayer.js | 4 ++-- src/mapml/layers/TemplatedFeaturesLayer.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mapml/layers/MapLayer.js b/src/mapml/layers/MapLayer.js index 649b93284..e62c48914 100644 --- a/src/mapml/layers/MapLayer.js +++ b/src/mapml/layers/MapLayer.js @@ -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}); } } }); @@ -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); diff --git a/src/mapml/layers/TemplatedFeaturesLayer.js b/src/mapml/layers/TemplatedFeaturesLayer.js index b198c1e6a..66a008cd8 100644 --- a/src/mapml/layers/TemplatedFeaturesLayer.js +++ b/src/mapml/layers/TemplatedFeaturesLayer.js @@ -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}); } }); } From 61a83d429bf56b658439f78753a4ea7b662c19d6 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Wed, 3 Feb 2021 11:31:46 -0500 Subject: [PATCH 08/16] Add bypass navigation --- src/mapml.css | 18 ++++++ src/mapml/layers/Crosshair.js | 11 +++- src/mapml/layers/FeatureLayer.js | 66 +++++++++++++++++++++- src/mapml/layers/TemplatedFeaturesLayer.js | 13 ++++- 4 files changed, 104 insertions(+), 4 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index 2f066f45c..9bd236d77 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -385,4 +385,22 @@ summary { display: block; position: absolute; z-index: 10000; +} + +.mapml-popup-button { + padding: 4px 4px 0 0; + border: none; + text-align: center; + width: 18px; + height: 14px; + font: 16px/14px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: bold; + background: transparent; + float: left; +} + +.mapml-focus-buttons { + margin-left: 4px; } \ No newline at end of file diff --git a/src/mapml/layers/Crosshair.js b/src/mapml/layers/Crosshair.js index 170e3306c..ad622adb1 100644 --- a/src/mapml/layers/Crosshair.js +++ b/src/mapml/layers/Crosshair.js @@ -34,7 +34,7 @@ export var Crosshair = L.Layer.extend({ this._isQueryable = false; map.on("layerchange layeradd layerremove overlayremove", this._toggleEvents, this); - L.DomEvent.on(map._container, "keydown keyup mousedown", this._isMapFocused, this); + L.DomEvent.on(map._container, "keydown keyup mousedown", this._onKey, this); this._addOrRemoveCrosshair(); @@ -69,11 +69,18 @@ export var Crosshair = L.Layer.extend({ return false; }, - _isMapFocused: function (e) { + _onKey: function (e) { + //set mapFocused = true if arrow buttons are used if (["keydown", "keyup"].includes(e.type) && e.target.classList.contains("leaflet-container") && [32, 37, 38, 39, 40, 187, 189].includes(+e.keyCode)) { this._mapFocused = true; + //set mapFocused = true if map is focued using tab } else if (e.type === "keyup" && e.target.classList.contains("leaflet-container") && +e.keyCode === 9) { this._mapFocused = true; + // set mapFocused = false and close all popups if tab or escape is used + } else if((e.type === "keyup" && e.target.classList.contains("leaflet-interactive") && +e.keyCode === 9) || +e.keyCode === 27){ + this._mapFocused = false; + this._map.closePopup(); + // set mapFocused = false if any other key is pressed } else { this._mapFocused = false; } diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index b1e04ddad..699a974b8 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -38,6 +38,7 @@ export var MapMLFeatures = L.FeatureGroup.extend({ onAdd: function(map){ L.FeatureGroup.prototype.onAdd.call(this, map); + map.on("popupopen", this._attachSkipButtons, this); this._updateTabIndex(); }, @@ -55,11 +56,74 @@ 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); + if(path._path){ + if(path._path.getAttribute("d") !== "M0 0"){ + path._path.setAttribute("tabindex", 0); + } else { + path._path.removeAttribute("tabindex"); + } + if(path._path.childElementCount === 0) { + let title = document.createElement("title"); + title.innerText = "Feature"; + path._path.appendChild(title); + } + } } } }, + _attachSkipButtons: function(e){ + if(!e.popup._container.querySelector('div[class="mapml-focus-buttons"]')){ + //add when popopen event happens instead + let div = L.DomUtil.create("div", "mapml-focus-buttons"); + let backButton = L.DomUtil.create('a',"mapml-popup-button", div); + backButton.href = '#'; + backButton.role = "button"; + backButton.title = "Skip Backwards"; + backButton.innerHTML = '❮'; + L.DomEvent.disableClickPropagation(backButton); + L.DomEvent.on(backButton, 'click', L.DomEvent.stop); + L.DomEvent.on(backButton, 'click', this._skipBackward, this); + + let forwardButton = L.DomUtil.create('a',"mapml-popup-button", div); + forwardButton.href = '#'; + forwardButton.role = "button"; + forwardButton.title = "Skip Forwards"; + forwardButton.innerHTML = '❯'; + L.DomEvent.disableClickPropagation(forwardButton); + L.DomEvent.on(forwardButton, 'click', L.DomEvent.stop); + L.DomEvent.on(forwardButton, 'click', this._skipForward, this); + e.popup._container.prepend(div); + } + + function focusFeature(focusEvent){ + if(focusEvent.originalEvent.path[0].title==="Skip Forwards" && e.popup._source._path.nextSibling && +focusEvent.originalEvent.keyCode === 9){ + L.DomEvent.stopPropagation(focusEvent); + e.popup._source._path.nextSibling.focus(); + } + }; + + this._map.on("keydown", focusFeature); + this._map.off("popupclose", (closeEvent)=>{ + if (closeEvent.popup === e.popup){ + this._map.off("keydown", focusFeature); + } + }); + + e.popup._container.querySelector("a").focus(); + + }, + + _skipBackward: function(e){ + this._map.closePopup(); + this._map._container.focus(); + }, + + _skipForward: function(e){ + this._map.closePopup(); + this._map._controlContainer.focus(); + }, + _handleMoveEnd : function(){ let mapZoom = this._map.getZoom(); if(mapZoom > this.zoomBounds.maxZoom || mapZoom < this.zoomBounds.minZoom){ diff --git a/src/mapml/layers/TemplatedFeaturesLayer.js b/src/mapml/layers/TemplatedFeaturesLayer.js index 66a008cd8..21a0c5794 100644 --- a/src/mapml/layers/TemplatedFeaturesLayer.js +++ b/src/mapml/layers/TemplatedFeaturesLayer.js @@ -125,7 +125,18 @@ export var TemplatedFeaturesLayer = L.Layer.extend({ 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); + if(layer._path){ + if(layer._path.getAttribute("d") !== "M0 0"){ + layer._path.setAttribute("tabindex", 0); + } else { + layer._path.removeAttribute("tabindex"); + } + if(layer._path.childElementCount === 0) { + let title = document.createElement("title"); + title.innerText = "Feature"; + layer._path.appendChild(title); + } + } } }, _updateZIndex: function () { From edc2cf0e1c2a6d44027cf5a17be882aaaa9ee529 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Wed, 3 Feb 2021 15:10:31 -0500 Subject: [PATCH 09/16] Query popup fix --- src/mapml/layers/FeatureLayer.js | 1 + test/e2e/core/keyboardInteraction.test.js | 9 --------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index 699a974b8..6428666b7 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -73,6 +73,7 @@ export var MapMLFeatures = L.FeatureGroup.extend({ }, _attachSkipButtons: function(e){ + if(!e.popup._source._path) return; if(!e.popup._container.querySelector('div[class="mapml-focus-buttons"]')){ //add when popopen event happens instead let div = L.DomUtil.create("div", "mapml-focus-buttons"); diff --git a/test/e2e/core/keyboardInteraction.test.js b/test/e2e/core/keyboardInteraction.test.js index de51ab648..dbe919408 100644 --- a/test/e2e/core/keyboardInteraction.test.js +++ b/test/e2e/core/keyboardInteraction.test.js @@ -71,15 +71,6 @@ jest.setTimeout(50000); }); }); describe("Tab Navigable Tests in " + browserType, () => { - function findFocusedNode(node) { - if (node.focused) - return node; - for (const child of node.children || []) { - const foundNode = findFocusedNode(child); - return foundNode; - } - return null; - } test("[" + browserType + "]" + " Tab focuses inline features", async () => { await page.click("div > div.leaflet-control-container > div.leaflet-top.leaflet-left > div.mapml-reload-button.leaflet-bar.leaflet-control > a"); await page.waitForTimeout(1000); From 3147842ee0746f49b55204d4504a6b8d23e0e7be Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Thu, 4 Feb 2021 13:11:21 -0500 Subject: [PATCH 10/16] Add feature count, move controls to bottom --- src/mapml.css | 16 ++++++++++++--- src/mapml/handlers/QueryHandler.js | 13 +++++++----- src/mapml/layers/FeatureLayer.js | 33 ++++++++++++++++++++---------- src/mapml/layers/MapLayer.js | 10 ++++++++- test/e2e/layers/mapMLFeatures.html | 1 + 5 files changed, 53 insertions(+), 20 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index 922829108..81737c943 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -389,7 +389,7 @@ summary { } .mapml-popup-button { - padding: 4px 4px 0 0; + padding: 0 4px 0 4px; border: none; text-align: center; width: 18px; @@ -399,9 +399,19 @@ summary { text-decoration: none; font-weight: bold; background: transparent; - float: left; + white-space: nowrap; } .mapml-focus-buttons { - margin-left: 4px; + padding-left: -4px; + white-space: nowrap; + overflow:auto; + display: inline; +} + +.mapml-feature-count { + display:inline; + white-space: nowrap; + text-align: center; + padding: 2px; } \ No newline at end of file diff --git a/src/mapml/handlers/QueryHandler.js b/src/mapml/handlers/QueryHandler.js index bbcd15653..356a86750 100644 --- a/src/mapml/handlers/QueryHandler.js +++ b/src/mapml/handlers/QueryHandler.js @@ -151,14 +151,15 @@ export var QueryHandler = L.Handler.extend({ }); f.addTo(map); - var c = document.createElement('iframe'); + let div = L.DomUtil.create("div", "mapml-popup-content"), + c = L.DomUtil.create("iframe"); c.csp = "script-src 'none'"; c.style = "border: none"; c.srcdoc = mapmldoc.querySelector('feature properties').innerHTML; - + div.appendChild(c); // passing a latlng to the popup is necessary for when there is no // geometry / null geometry - layer.bindPopup(c, popupOptions).openPopup(loc); + layer.bindPopup(div, popupOptions).openPopup(loc); layer.on('popupclose', function() { map.removeLayer(f); }); @@ -166,11 +167,13 @@ export var QueryHandler = L.Handler.extend({ } function handleOtherResponse(response, layer, loc) { return response.text().then(text => { - var c = document.createElement('iframe'); + let div = L.DomUtil.create("div", "mapml-popup-content"), + c = L.DomUtil.create("iframe"); c.csp = "script-src 'none'"; c.style = "border: none"; c.srcdoc = text; - layer.bindPopup(c, popupOptions).openPopup(loc); + div.appendChild(c); + layer.bindPopup(div, popupOptions).openPopup(loc); }); } } diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index 6428666b7..def07a294 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -77,32 +77,46 @@ export var MapMLFeatures = L.FeatureGroup.extend({ if(!e.popup._container.querySelector('div[class="mapml-focus-buttons"]')){ //add when popopen event happens instead let div = L.DomUtil.create("div", "mapml-focus-buttons"); + let backButton = L.DomUtil.create('a',"mapml-popup-button", div); backButton.href = '#'; backButton.role = "button"; - backButton.title = "Skip Backwards"; - backButton.innerHTML = '❮'; + backButton.title = "Focus Map"; + backButton.innerHTML = '|❮'; L.DomEvent.disableClickPropagation(backButton); L.DomEvent.on(backButton, 'click', L.DomEvent.stop); L.DomEvent.on(backButton, 'click', this._skipBackward, this); + + let featureCount = L.DomUtil.create("p", "mapml-feature-count", div), currentFeature = 1; + + for(let feature of e.popup._source._path.parentNode.children){ + if(feature === e.popup._source._path)break; + currentFeature++; + } + featureCount.innerText = currentFeature+"/"+e.popup._source._path.parentNode.childElementCount; let forwardButton = L.DomUtil.create('a',"mapml-popup-button", div); forwardButton.href = '#'; forwardButton.role = "button"; - forwardButton.title = "Skip Forwards"; - forwardButton.innerHTML = '❯'; + forwardButton.title = "Focus Controls"; + forwardButton.innerHTML = '❯|'; L.DomEvent.disableClickPropagation(forwardButton); L.DomEvent.on(forwardButton, 'click', L.DomEvent.stop); L.DomEvent.on(forwardButton, 'click', this._skipForward, this); - e.popup._container.prepend(div); + + let divider = L.DomUtil.create("hr"); + divider.style.borderTop = "1px solid #bbb"; + + e.popup._content.appendChild(divider); + e.popup._content.appendChild(div); } function focusFeature(focusEvent){ - if(focusEvent.originalEvent.path[0].title==="Skip Forwards" && e.popup._source._path.nextSibling && +focusEvent.originalEvent.keyCode === 9){ + if(focusEvent.originalEvent.path[0].title==="Focus Controls" && e.popup._source._path.nextSibling && +focusEvent.originalEvent.keyCode === 9){ L.DomEvent.stopPropagation(focusEvent); - e.popup._source._path.nextSibling.focus(); + e.popup._source._path.focus(); } - }; + } this._map.on("keydown", focusFeature); this._map.off("popupclose", (closeEvent)=>{ @@ -110,9 +124,6 @@ export var MapMLFeatures = L.FeatureGroup.extend({ this._map.off("keydown", focusFeature); } }); - - e.popup._container.querySelector("a").focus(); - }, _skipBackward: function(e){ diff --git a/src/mapml/layers/MapLayer.js b/src/mapml/layers/MapLayer.js index e62c48914..57992b3f3 100644 --- a/src/mapml/layers/MapLayer.js +++ b/src/mapml/layers/MapLayer.js @@ -93,6 +93,7 @@ export var MapMLLayer = L.Layer.extend({ // need to parse as HTML to preserve semantics and styles if (properties) { var c = document.createElement('div'); + c.classList.add("mapml-popup-content"); c.insertAdjacentHTML('afterbegin', properties.innerHTML); geometry.bindPopup(c, {autoPan:false, closeButton: false}); } @@ -122,6 +123,7 @@ export var MapMLLayer = L.Layer.extend({ // need to parse as HTML to preserve semantics and styles if (properties) { var c = document.createElement('div'); + c.classList.add("mapml-popup-content"); c.insertAdjacentHTML('afterbegin', properties.innerHTML); geometry.bindPopup(c, {autoPan:false, closeButton: false}); } @@ -189,6 +191,7 @@ export var MapMLLayer = L.Layer.extend({ setTimeout(() => { map.fire('checkdisabled'); }, 0); + map.on("popupopen", this._focusPopup, this); }, _validProjection : function(map){ @@ -1111,7 +1114,12 @@ export var MapMLLayer = L.Layer.extend({ if (this._templatedLayer && this._templatedLayer._queries) { return this._templatedLayer._queries; } - } + }, + _focusPopup: function(e){ + let content = e.popup._container.getElementsByClassName("mapml-popup-content")[0]; + content.setAttribute("tabindex", "-1"); + content.focus(); + }, }); export var mapMLLayer = function (url, node, options) { if (!url && !node) return null; diff --git a/test/e2e/layers/mapMLFeatures.html b/test/e2e/layers/mapMLFeatures.html index e704a2df0..7b1cea116 100644 --- a/test/e2e/layers/mapMLFeatures.html +++ b/test/e2e/layers/mapMLFeatures.html @@ -43,6 +43,7 @@

Test

+ test
From 8bc75ce61295b6d58db6b3b27230d43d11723fcd Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Thu, 4 Feb 2021 13:45:30 -0500 Subject: [PATCH 11/16] Test update to consider skip buttons --- test/e2e/core/keyboardInteraction.test.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/test/e2e/core/keyboardInteraction.test.js b/test/e2e/core/keyboardInteraction.test.js index dbe919408..032104666 100644 --- a/test/e2e/core/keyboardInteraction.test.js +++ b/test/e2e/core/keyboardInteraction.test.js @@ -7,7 +7,7 @@ jest.setTimeout(50000); () => { beforeAll(async () => { browser = await playwright[browserType].launch({ - headless: ISHEADLESS, + headless: false,//ISHEADLESS, slowMo: 50, }); context = await browser.newContext(); @@ -48,6 +48,8 @@ jest.setTimeout(50000); test("[" + browserType + "]" + " Crosshair hidden on esc + tab out", async () => { await page.keyboard.press("Escape"); const afterEsc = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); + await page.click("body"); + await page.keyboard.press("Tab"); await page.keyboard.press("ArrowUp"); await page.keyboard.press("Tab"); @@ -58,7 +60,8 @@ jest.setTimeout(50000); }); test("[" + browserType + "]" + " Crosshair hidden when queryable layer is unselected, shows on reselect", async () => { - await page.click("body > mapml-viewer"); + await page.click("body"); + await page.keyboard.press("Tab"); await page.keyboard.press("ArrowUp"); await page.evaluateHandle(() => document.querySelector("layer-").removeAttribute("checked")); const afterUncheck = await page.$eval("div > div.mapml-crosshair", (div) => div.style.visibility); @@ -72,10 +75,8 @@ jest.setTimeout(50000); }); describe("Tab Navigable Tests in " + browserType, () => { test("[" + browserType + "]" + " Tab focuses inline features", async () => { - await page.click("div > div.leaflet-control-container > div.leaflet-top.leaflet-left > div.mapml-reload-button.leaflet-bar.leaflet-control > a"); - await page.waitForTimeout(1000); - await page.click("body > mapml-viewer"); - await page.keyboard.press("Escape"); + await page.click("body"); + await page.keyboard.press("Tab"); await page.keyboard.press("Tab"); const aHandle = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); @@ -90,13 +91,13 @@ jest.setTimeout(50000); const focusedNext = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandleNext)).jsonValue(); expect(focused).toEqual("M330 83L553 83L553 339L330 339z"); - expect(focusedNext).toEqual("M-53 451L153 508L113 146L-53 191z"); + expect(focusedNext).toEqual("M-53 393L140 393L113 146L-53 191z"); }); test("[" + browserType + "]" + " Tab focuses fetched features", async () => { await page.evaluateHandle(() => document.getElementById("vector").setAttribute("checked", "")); - await page.click("body > mapml-viewer"); - await page.keyboard.press("Escape"); + await page.click("body"); + await page.keyboard.press("Tab"); await page.keyboard.press("Tab"); await page.keyboard.press("Tab"); @@ -113,7 +114,7 @@ jest.setTimeout(50000); const resultHandleNext = await page.evaluateHandle(root => root.activeElement, nextHandleNext); const focusedNext = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandleNext)).jsonValue(); - expect(focused).toEqual("M190 357L203 355L209 374L213 379L211 380L213 391L193 393L193 395L195 396L194 400L191 399L191 397L189 398L189 400L187 400L185 386L184 357z"); + expect(focused).toEqual("M190 357L203 355L209 374L213 379L211 380L213 391L186 393L184 357z"); expect(focusedNext).toEqual("M-30 139L-29 138L-31 140zM-30 136L-29 138L-31 138zM-29 126L-28 127L-30 127zM-32 125L-30 131L-32 137L-31 138L-34 141L-34 138L-36 136L-36 139L-37 136L-34 132L-35 131L-33 126zM-36 130L-35 131L-37 132zM-31 121L-30 120L-28 122L-28 127L-31 125L-31 121zM-33 123L-34 122L-32 120L-31 124L-32 123L-35 126L-35 124zM-36 123L-37 124L-37 118L-35 113L-32 114L-34 120zM-27 110L-26 115L-27 114L-28 118L-32 119L-33 118L-30 115L-27 108zM-36 113L-36 114zM-33 106L-29 110L-31 112L-31 115L-33 111L-34 113L-35 112L-35 108L-33 105zM3 6L7 13L11 15L13 23L-37 80L-35 84L-31 85L-33 86L-33 99L-29 99L-27 97L-22 98L-22 117L-24 128L-20 136L-23 142L-28 144L-29 143L-26 140L-29 139L-30 136L-28 135L-30 136L-30 134L-28 132L-30 132L-26 127L-28 120L-26 117L-27 107L-25 102L-29 109L-29 103L-31 100L-31 106L-35 103L-36 92L-38 89L-34 86L-38 86L-40 84L-43 77L-48 74L-48 69L-50 69L-50 66L-53 64L-50 65L-50 60L-53 58L-53 -20L-49 -17L-53 -14L-47 -7L-48 -10L-45 -14L-44 -13L-47 -9L-43 -12L-45 -18L-42 -20L-40 -31L-36 -31L-33 -26L-30 -25L-19 -26L-20 -25L-18 -23L-13 -23L-12 -20L-5 -18L-4 -15L-7 -14L-5 -14L-5 -11L-1 -7L-3 -6L-2 -2L2 1L3 5z"); }); }); From 69478aae73ac2d6224441e2a93f3186d326c7cc8 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Thu, 4 Feb 2021 14:14:43 -0500 Subject: [PATCH 12/16] Test update --- test/e2e/core/keyboardInteraction.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/core/keyboardInteraction.test.js b/test/e2e/core/keyboardInteraction.test.js index 032104666..91054bdc3 100644 --- a/test/e2e/core/keyboardInteraction.test.js +++ b/test/e2e/core/keyboardInteraction.test.js @@ -7,7 +7,7 @@ jest.setTimeout(50000); () => { beforeAll(async () => { browser = await playwright[browserType].launch({ - headless: false,//ISHEADLESS, + headless: ISHEADLESS, slowMo: 50, }); context = await browser.newContext(); From 7326fa18440f86674fd422caa8a6e7b210b322b7 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Thu, 4 Feb 2021 16:41:01 -0500 Subject: [PATCH 13/16] Adds next and previous focus buttons --- src/mapml.css | 2 - src/mapml/layers/FeatureLayer.js | 48 ++++++++++++++++++++--- src/mapml/layers/MapLayer.js | 4 +- test/e2e/core/keyboardInteraction.test.js | 24 ++++++++++++ 4 files changed, 68 insertions(+), 10 deletions(-) diff --git a/src/mapml.css b/src/mapml.css index 50c9147bf..634c9b71d 100644 --- a/src/mapml.css +++ b/src/mapml.css @@ -415,9 +415,7 @@ summary { } .mapml-focus-buttons { - padding-left: -4px; white-space: nowrap; - overflow:auto; display: inline; } diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index def07a294..86fa090c7 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -87,13 +87,31 @@ export var MapMLFeatures = L.FeatureGroup.extend({ L.DomEvent.on(backButton, 'click', L.DomEvent.stop); L.DomEvent.on(backButton, 'click', this._skipBackward, this); - let featureCount = L.DomUtil.create("p", "mapml-feature-count", div), currentFeature = 1; + let previousButton = L.DomUtil.create('a', "mapml-popup-button", div); + previousButton.href = '#'; + previousButton.role = "button"; + previousButton.title = "Previous Feature"; + previousButton.innerHTML = "❮"; + L.DomEvent.disableClickPropagation(previousButton); + L.DomEvent.on(previousButton, 'click', L.DomEvent.stop); + L.DomEvent.on(previousButton, 'click', this._previousFeature, e.popup); - for(let feature of e.popup._source._path.parentNode.children){ - if(feature === e.popup._source._path)break; - currentFeature++; - } - featureCount.innerText = currentFeature+"/"+e.popup._source._path.parentNode.childElementCount; + let featureCount = L.DomUtil.create("p", "mapml-feature-count", div), currentFeature = 1; + featureCount.innerText = currentFeature+"/1"; + //for(let feature of e.popup._source._path.parentNode.children){ + // if(feature === e.popup._source._path)break; + // currentFeature++; + //} + //featureCount.innerText = currentFeature+"/"+e.popup._source._path.parentNode.childElementCount; + + let nextButton = L.DomUtil.create('a', "mapml-popup-button", div); + nextButton.href = '#'; + nextButton.role = "button"; + nextButton.title = "Next Feature"; + nextButton.innerHTML = "❯"; + L.DomEvent.disableClickPropagation(nextButton); + L.DomEvent.on(nextButton, 'click', L.DomEvent.stop); + L.DomEvent.on(nextButton, 'click', this._nextFeature, e.popup); let forwardButton = L.DomUtil.create('a',"mapml-popup-button", div); forwardButton.href = '#'; @@ -130,6 +148,24 @@ export var MapMLFeatures = L.FeatureGroup.extend({ this._map.closePopup(); this._map._container.focus(); }, + + _previousFeature: function(e){ + this._map.closePopup(); + if(this._source._path.previousSibling){ + this._source._path.previousSibling.focus(); + } else { + this._source._path.focus(); + } + }, + + _nextFeature: function(e){ + this._map.closePopup(); + if(this._source._path.nextSibling){ + this._source._path.nextSibling.focus(); + } else { + this._source._path.focus(); + } + }, _skipForward: function(e){ this._map.closePopup(); diff --git a/src/mapml/layers/MapLayer.js b/src/mapml/layers/MapLayer.js index 07b6f9e11..d78987052 100644 --- a/src/mapml/layers/MapLayer.js +++ b/src/mapml/layers/MapLayer.js @@ -95,7 +95,7 @@ export var MapMLLayer = L.Layer.extend({ var c = document.createElement('div'); c.classList.add("mapml-popup-content"); c.insertAdjacentHTML('afterbegin', properties.innerHTML); - geometry.bindPopup(c, {autoPan:false, closeButton: false}); + geometry.bindPopup(c, {autoPan:false, closeButton: false, minWidth: 108}); } } }); @@ -125,7 +125,7 @@ export var MapMLLayer = L.Layer.extend({ var c = document.createElement('div'); c.classList.add("mapml-popup-content"); c.insertAdjacentHTML('afterbegin', properties.innerHTML); - geometry.bindPopup(c, {autoPan:false, closeButton: false}); + geometry.bindPopup(c, {autoPan:false, closeButton: false, minWidth: 108}); } } }).addTo(map); diff --git a/test/e2e/core/keyboardInteraction.test.js b/test/e2e/core/keyboardInteraction.test.js index 91054bdc3..fc742ebb0 100644 --- a/test/e2e/core/keyboardInteraction.test.js +++ b/test/e2e/core/keyboardInteraction.test.js @@ -118,6 +118,30 @@ jest.setTimeout(50000); expect(focusedNext).toEqual("M-30 139L-29 138L-31 140zM-30 136L-29 138L-31 138zM-29 126L-28 127L-30 127zM-32 125L-30 131L-32 137L-31 138L-34 141L-34 138L-36 136L-36 139L-37 136L-34 132L-35 131L-33 126zM-36 130L-35 131L-37 132zM-31 121L-30 120L-28 122L-28 127L-31 125L-31 121zM-33 123L-34 122L-32 120L-31 124L-32 123L-35 126L-35 124zM-36 123L-37 124L-37 118L-35 113L-32 114L-34 120zM-27 110L-26 115L-27 114L-28 118L-32 119L-33 118L-30 115L-27 108zM-36 113L-36 114zM-33 106L-29 110L-31 112L-31 115L-33 111L-34 113L-35 112L-35 108L-33 105zM3 6L7 13L11 15L13 23L-37 80L-35 84L-31 85L-33 86L-33 99L-29 99L-27 97L-22 98L-22 117L-24 128L-20 136L-23 142L-28 144L-29 143L-26 140L-29 139L-30 136L-28 135L-30 136L-30 134L-28 132L-30 132L-26 127L-28 120L-26 117L-27 107L-25 102L-29 109L-29 103L-31 100L-31 106L-35 103L-36 92L-38 89L-34 86L-38 86L-40 84L-43 77L-48 74L-48 69L-50 69L-50 66L-53 64L-50 65L-50 60L-53 58L-53 -20L-49 -17L-53 -14L-47 -7L-48 -10L-45 -14L-44 -13L-47 -9L-43 -12L-45 -18L-42 -20L-40 -31L-36 -31L-33 -26L-30 -25L-19 -26L-20 -25L-18 -23L-13 -23L-12 -20L-5 -18L-4 -15L-7 -14L-5 -14L-5 -11L-1 -7L-3 -6L-2 -2L2 1L3 5z"); }); }); + + /* describe("Feature Popup Tab Navigation Tests in " + browserType, () => { + test("[" + browserType + "]" + " Tab focuses inline features", async () => { + await page.click("div > div.leaflet-control-container > div.leaflet-top.leaflet-left > div.mapml-reload-button.leaflet-bar.leaflet-control > a"); + await page.evaluateHandle(() => document.getElementById("vector").removeAttribute("checked")); + await page.click("body"); + await page.keyboard.press("Tab"); + + await page.keyboard.press("Tab"); + const aHandle = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nextHandle = await page.evaluateHandle(doc => doc.shadowRoot, aHandle); + const resultHandle = await page.evaluateHandle(root => root.activeElement, nextHandle); + const focused = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandle)).jsonValue(); + + await page.keyboard.press("Tab"); + const aHandleNext = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nextHandleNext = await page.evaluateHandle(doc => doc.shadowRoot, aHandleNext); + const resultHandleNext = await page.evaluateHandle(root => root.activeElement, nextHandleNext); + const focusedNext = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandleNext)).jsonValue(); + + expect(focused).toEqual("M330 83L553 83L553 339L330 339z"); + expect(focusedNext).toEqual("M-53 393L140 393L113 146L-53 191z"); + }); + }); */ } ); } From 19aaf5ee484cda03f41f790cd69da40e66ba9fb4 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Fri, 5 Feb 2021 10:37:30 -0500 Subject: [PATCH 14/16] Add tests for keyboard interaction --- src/mapml/layers/FeatureLayer.js | 16 +-- test/e2e/core/keyboardInteraction.html | 3 +- test/e2e/core/keyboardInteraction.test.js | 126 ++++++++++++++++++---- 3 files changed, 115 insertions(+), 30 deletions(-) diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index 86fa090c7..d7b074679 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -130,16 +130,20 @@ export var MapMLFeatures = L.FeatureGroup.extend({ } function focusFeature(focusEvent){ - if(focusEvent.originalEvent.path[0].title==="Focus Controls" && e.popup._source._path.nextSibling && +focusEvent.originalEvent.keyCode === 9){ - L.DomEvent.stopPropagation(focusEvent); + if(focusEvent.originalEvent.path[0].title==="Focus Controls" && +focusEvent.originalEvent.keyCode === 9){ + L.DomEvent.stop(focusEvent); + e.popup._source._path.focus(); + } else if(focusEvent.originalEvent.shiftKey && +focusEvent.originalEvent.keyCode === 9){ + e.target.closePopup(e.popup); + L.DomEvent.stop(focusEvent); e.popup._source._path.focus(); } - } + }/* */ - this._map.on("keydown", focusFeature); - this._map.off("popupclose", (closeEvent)=>{ + e.target.on("keydown", focusFeature); + e.target.off("popupclose", (closeEvent)=>{ if (closeEvent.popup === e.popup){ - this._map.off("keydown", focusFeature); + e.target.off("keydown", focusFeature); } }); }, diff --git a/test/e2e/core/keyboardInteraction.html b/test/e2e/core/keyboardInteraction.html index 0c46ea364..fbe8add98 100644 --- a/test/e2e/core/keyboardInteraction.html +++ b/test/e2e/core/keyboardInteraction.html @@ -10,7 +10,7 @@ - +

Test

+ test
diff --git a/test/e2e/core/keyboardInteraction.test.js b/test/e2e/core/keyboardInteraction.test.js index fc742ebb0..9866c958f 100644 --- a/test/e2e/core/keyboardInteraction.test.js +++ b/test/e2e/core/keyboardInteraction.test.js @@ -119,29 +119,109 @@ jest.setTimeout(50000); }); }); - /* describe("Feature Popup Tab Navigation Tests in " + browserType, () => { - test("[" + browserType + "]" + " Tab focuses inline features", async () => { - await page.click("div > div.leaflet-control-container > div.leaflet-top.leaflet-left > div.mapml-reload-button.leaflet-bar.leaflet-control > a"); - await page.evaluateHandle(() => document.getElementById("vector").removeAttribute("checked")); - await page.click("body"); - await page.keyboard.press("Tab"); - - await page.keyboard.press("Tab"); - const aHandle = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); - const nextHandle = await page.evaluateHandle(doc => doc.shadowRoot, aHandle); - const resultHandle = await page.evaluateHandle(root => root.activeElement, nextHandle); - const focused = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandle)).jsonValue(); - - await page.keyboard.press("Tab"); - const aHandleNext = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); - const nextHandleNext = await page.evaluateHandle(doc => doc.shadowRoot, aHandleNext); - const resultHandleNext = await page.evaluateHandle(root => root.activeElement, nextHandleNext); - const focusedNext = await (await page.evaluateHandle(elem => elem.getAttribute("d"), resultHandleNext)).jsonValue(); - - expect(focused).toEqual("M330 83L553 83L553 339L330 339z"); - expect(focusedNext).toEqual("M-53 393L140 393L113 146L-53 191z"); - }); - }); */ + describe("Feature Popup Tab Navigation Tests in " + browserType, () => { + test("[" + browserType + "]" + " Inline features popup focus order", async () => { + await page.click("div > div.leaflet-control-container > div.leaflet-top.leaflet-left > div.mapml-reload-button.leaflet-bar.leaflet-control > a"); + await page.evaluateHandle(() => document.getElementById("vector").removeAttribute("checked")); + await page.evaluateHandle(() => document.getElementById("query").removeAttribute("checked")); + await page.click("body"); + await page.keyboard.press("Tab"); + + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + const h = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh = await page.evaluateHandle(doc => doc.shadowRoot, h); + const rh = await page.evaluateHandle(root => root.activeElement, nh); + const f = await (await page.evaluateHandle(elem => elem.className, rh)).jsonValue(); + + await page.keyboard.press("Tab"); + const h2 = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh2 = await page.evaluateHandle(doc => doc.shadowRoot, h2); + const rh2 = await page.evaluateHandle(root => root.activeElement, nh2); + const f2 = await (await page.evaluateHandle(elem => elem.tagName, rh2)).jsonValue(); + + await page.keyboard.press("Tab"); + const h3 = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh3 = await page.evaluateHandle(doc => doc.shadowRoot, h3); + const rh3 = await page.evaluateHandle(root => root.activeElement, nh3); + const f3 = await (await page.evaluateHandle(elem => elem.title, rh3)).jsonValue(); + + await page.keyboard.press("Tab"); + const h4 = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh4 = await page.evaluateHandle(doc => doc.shadowRoot, h4); + const rh4 = await page.evaluateHandle(root => root.activeElement, nh4); + const f4 = await (await page.evaluateHandle(elem => elem.title, rh4)).jsonValue(); + + await page.keyboard.press("Tab"); + const h5 = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh5 = await page.evaluateHandle(doc => doc.shadowRoot, h5); + const rh5 = await page.evaluateHandle(root => root.activeElement, nh5); + const f5 = await (await page.evaluateHandle(elem => elem.title, rh5)).jsonValue(); + + await page.keyboard.press("Tab"); + const h6 = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh6 = await page.evaluateHandle(doc => doc.shadowRoot, h6); + const rh6 = await page.evaluateHandle(root => root.activeElement, nh6); + const f6 = await (await page.evaluateHandle(elem => elem.title, rh6)).jsonValue(); + + expect(f).toEqual("mapml-popup-content"); + expect(f2).toEqual("A"); + expect(f3).toEqual("Focus Map"); + expect(f4).toEqual("Previous Feature"); + expect(f5).toEqual("Next Feature"); + expect(f6).toEqual("Focus Controls"); + }); + + test("[" + browserType + "]" + " Tab to next feature after tabbing out of popup", async () => { + await page.keyboard.press("Tab"); + const h = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh = await page.evaluateHandle(doc => doc.shadowRoot, h); + const rh = await page.evaluateHandle(root => root.activeElement, nh); + const f = await (await page.evaluateHandle(elem => elem.getAttribute("d"), rh)).jsonValue(); + + expect(f).toEqual("M-53 451L153 508L113 146L-53 191z"); + }); + + test("[" + browserType + "]" + " Shift + Tab to previous feature while popup open", async () => { + await page.keyboard.press("Enter"); + await page.keyboard.press("Shift+Tab"); + const h = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh = await page.evaluateHandle(doc => doc.shadowRoot, h); + const rh = await page.evaluateHandle(root => root.activeElement, nh); + const f = await (await page.evaluateHandle(elem => elem.getAttribute("d"), rh)).jsonValue(); + + expect(f).toEqual("M330 83L553 83L553 339L330 339z"); + }); + + test("[" + browserType + "]" + " Previous feature button focuses previous feature", async () => { + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + const h = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh = await page.evaluateHandle(doc => doc.shadowRoot, h); + const rh = await page.evaluateHandle(root => root.activeElement, nh); + const f = await (await page.evaluateHandle(elem => elem.getAttribute("d"), rh)).jsonValue(); + + expect(f).toEqual("M330 83L553 83L553 339L330 339z"); + }); + + test("[" + browserType + "]" + " Next feature button focuses next feature", async () => { + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Tab"); + await page.keyboard.press("Enter"); + const h = await page.evaluateHandle(() => document.querySelector("mapml-viewer")); + const nh = await page.evaluateHandle(doc => doc.shadowRoot, h); + const rh = await page.evaluateHandle(root => root.activeElement, nh); + const f = await (await page.evaluateHandle(elem => elem.getAttribute("d"), rh)).jsonValue(); + + expect(f).toEqual("M285 373L460 380L468 477L329 459z"); + }); + }); } ); } From d227589951d8e08af23103aa607ca973d49c4747 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Fri, 5 Feb 2021 11:40:43 -0500 Subject: [PATCH 15/16] Rename variables and add comments --- .github/workflows/sync.yml | 2 +- src/mapml/layers/FeatureLayer.js | 46 +++++++++++++++++++------------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 15410d286..eb2001a78 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -29,5 +29,5 @@ jobs: destination_folder: 'dist' user_email: aayub041@uottawa.ca user_name: 'ahmadayubi' - commit_msg: 'Sync MapML Build' + commit_msg: '[AUTO] Sync MapML Build' destination_branch: main diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index d7b074679..fbe3bb771 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -78,15 +78,17 @@ export var MapMLFeatures = L.FeatureGroup.extend({ //add when popopen event happens instead let div = L.DomUtil.create("div", "mapml-focus-buttons"); - let backButton = L.DomUtil.create('a',"mapml-popup-button", div); - backButton.href = '#'; - backButton.role = "button"; - backButton.title = "Focus Map"; - backButton.innerHTML = '|❮'; - L.DomEvent.disableClickPropagation(backButton); - L.DomEvent.on(backButton, 'click', L.DomEvent.stop); - L.DomEvent.on(backButton, 'click', this._skipBackward, this); - + // creates |< button, focuses map + let mapFocusButton = L.DomUtil.create('a',"mapml-popup-button", div); + mapFocusButton.href = '#'; + mapFocusButton.role = "button"; + mapFocusButton.title = "Focus Map"; + mapFocusButton.innerHTML = '|❮'; + L.DomEvent.disableClickPropagation(mapFocusButton); + L.DomEvent.on(mapFocusButton, 'click', L.DomEvent.stop); + L.DomEvent.on(mapFocusButton, 'click', this._skipBackward, this); + + // creates < button, focuses previous feature, if none exists focuses the current feature let previousButton = L.DomUtil.create('a', "mapml-popup-button", div); previousButton.href = '#'; previousButton.role = "button"; @@ -96,6 +98,7 @@ export var MapMLFeatures = L.FeatureGroup.extend({ L.DomEvent.on(previousButton, 'click', L.DomEvent.stop); L.DomEvent.on(previousButton, 'click', this._previousFeature, e.popup); + // static feature counter that 1/1 let featureCount = L.DomUtil.create("p", "mapml-feature-count", div), currentFeature = 1; featureCount.innerText = currentFeature+"/1"; //for(let feature of e.popup._source._path.parentNode.children){ @@ -104,6 +107,7 @@ export var MapMLFeatures = L.FeatureGroup.extend({ //} //featureCount.innerText = currentFeature+"/"+e.popup._source._path.parentNode.childElementCount; + // creates > button, focuses next feature, if none exists focuses the current feature let nextButton = L.DomUtil.create('a', "mapml-popup-button", div); nextButton.href = '#'; nextButton.role = "button"; @@ -113,14 +117,15 @@ export var MapMLFeatures = L.FeatureGroup.extend({ L.DomEvent.on(nextButton, 'click', L.DomEvent.stop); L.DomEvent.on(nextButton, 'click', this._nextFeature, e.popup); - let forwardButton = L.DomUtil.create('a',"mapml-popup-button", div); - forwardButton.href = '#'; - forwardButton.role = "button"; - forwardButton.title = "Focus Controls"; - forwardButton.innerHTML = '❯|'; - L.DomEvent.disableClickPropagation(forwardButton); - L.DomEvent.on(forwardButton, 'click', L.DomEvent.stop); - L.DomEvent.on(forwardButton, 'click', this._skipForward, this); + // creates >| button, focuses map controls + let controlFocusButton = L.DomUtil.create('a',"mapml-popup-button", div); + controlFocusButton.href = '#'; + controlFocusButton.role = "button"; + controlFocusButton.title = "Focus Controls"; + controlFocusButton.innerHTML = '❯|'; + L.DomEvent.disableClickPropagation(controlFocusButton); + L.DomEvent.on(controlFocusButton, 'click', L.DomEvent.stop); + L.DomEvent.on(controlFocusButton, 'click', this._skipForward, this); let divider = L.DomUtil.create("hr"); divider.style.borderTop = "1px solid #bbb"; @@ -129,6 +134,7 @@ export var MapMLFeatures = L.FeatureGroup.extend({ e.popup._content.appendChild(div); } + // When popup is open, what gets focused with tab needs to be done using JS as the DOM order is not in an accessibility friendly manner function focusFeature(focusEvent){ if(focusEvent.originalEvent.path[0].title==="Focus Controls" && +focusEvent.originalEvent.keyCode === 9){ L.DomEvent.stop(focusEvent); @@ -138,9 +144,13 @@ export var MapMLFeatures = L.FeatureGroup.extend({ L.DomEvent.stop(focusEvent); e.popup._source._path.focus(); } - }/* */ + } + // e.target = this._map + // Looks for keydown, more specifically tab and shift tab e.target.on("keydown", focusFeature); + + // if popup closes then the focusFeature handler can be removed e.target.off("popupclose", (closeEvent)=>{ if (closeEvent.popup === e.popup){ e.target.off("keydown", focusFeature); From 8b3b1ef4f07a31fc271bf04a2af359ca881a8097 Mon Sep 17 00:00:00 2001 From: Ahmad Ayubi Date: Fri, 5 Feb 2021 11:50:45 -0500 Subject: [PATCH 16/16] Remove handlers on close --- src/mapml/layers/FeatureLayer.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/mapml/layers/FeatureLayer.js b/src/mapml/layers/FeatureLayer.js index fbe3bb771..1ddf8dfc0 100644 --- a/src/mapml/layers/FeatureLayer.js +++ b/src/mapml/layers/FeatureLayer.js @@ -146,16 +146,18 @@ export var MapMLFeatures = L.FeatureGroup.extend({ } } + function removeHandlers(removeEvent){ + if (removeEvent.popup === e.popup){ + e.target.off("keydown", focusFeature); + e.target.off("popupclose", removeHandlers); + } + } // e.target = this._map // Looks for keydown, more specifically tab and shift tab e.target.on("keydown", focusFeature); // if popup closes then the focusFeature handler can be removed - e.target.off("popupclose", (closeEvent)=>{ - if (closeEvent.popup === e.popup){ - e.target.off("keydown", focusFeature); - } - }); + e.target.on("popupclose", removeHandlers); }, _skipBackward: function(e){