Skip to content

Commit 8ee62d0

Browse files
authored
Navigate features using arrow keys (#679)
* Navigate features using arrow keys, update existing tests * Return to first/last feature when end/start of index is reached * Always focus center feature on refocus, allow navigation with feature index on * Update tests from rebase * Add extra navigation tests
1 parent 6ec8bc9 commit 8ee62d0

File tree

7 files changed

+96
-42
lines changed

7 files changed

+96
-42
lines changed

src/mapml/features/featureGroup.js

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,36 +59,48 @@ export var FeatureGroup = L.FeatureGroup.extend({
5959
* @private
6060
*/
6161
_handleFocus: function(e) {
62-
if((e.keyCode === 9 || e.keyCode === 16 || e.keyCode === 27) && e.type === "keydown"){
62+
if(([9, 16, 27, 37, 38, 39, 40].includes(e.keyCode)) && e.type === "keydown"){
6363
let index = this._map.featureIndex.currentIndex;
64-
if(e.keyCode === 9 && e.shiftKey) {
65-
if(index === this._map.featureIndex.inBoundFeatures.length - 1)
66-
this._map.featureIndex.inBoundFeatures[index].path.setAttribute("tabindex", -1);
67-
if(index !== 0){
68-
L.DomEvent.stop(e);
64+
// Down/right arrow keys replicate tabbing through the feature index
65+
// Up/left arrow keys replicate shift-tabbing through the feature index
66+
if(e.keyCode === 37 || e.keyCode === 38) {
67+
L.DomEvent.stop(e);
68+
this._map.featureIndex.inBoundFeatures[index].path.setAttribute("tabindex", -1);
69+
if(index === 0) {
70+
this._map.featureIndex.inBoundFeatures[this._map.featureIndex.inBoundFeatures.length - 1].path.focus();
71+
this._map.featureIndex.currentIndex = this._map.featureIndex.inBoundFeatures.length - 1;
72+
} else {
6973
this._map.featureIndex.inBoundFeatures[index - 1].path.focus();
7074
this._map.featureIndex.currentIndex--;
7175
}
72-
} else if (e.keyCode === 9) {
73-
if(index !== this._map.featureIndex.inBoundFeatures.length - 1) {
74-
L.DomEvent.stop(e);
76+
} else if (e.keyCode === 39 || e.keyCode === 40) {
77+
L.DomEvent.stop(e);
78+
this._map.featureIndex.inBoundFeatures[index].path.setAttribute("tabindex", -1);
79+
if(index === this._map.featureIndex.inBoundFeatures.length - 1) {
80+
this._map.featureIndex.inBoundFeatures[0].path.focus();
81+
this._map.featureIndex.currentIndex = 0;
82+
} else {
7583
this._map.featureIndex.inBoundFeatures[index + 1].path.focus();
7684
this._map.featureIndex.currentIndex++;
77-
} else {
78-
this._map.featureIndex.inBoundFeatures[0].path.setAttribute("tabindex", -1);
79-
this._map.featureIndex.inBoundFeatures[index].path.setAttribute("tabindex", 0);
8085
}
81-
} else if(e.keyCode === 27 && this._map.options.mapEl.shadowRoot.activeElement.nodeName === "g"){
82-
this._map.featureIndex.currentIndex = 0;
86+
} else if(e.keyCode === 27){
87+
let shadowRoot = this._map.options.mapEl.shadowRoot ? this._map.options.mapEl.shadowRoot :
88+
this._map.options.mapEl.querySelector(".mapml-web-map").shadowRoot;
89+
if(shadowRoot.activeElement.nodeName !== "g") return;
8390
this._map._container.focus();
91+
} else if (e.keyCode === 9) {
92+
let obj = this;
93+
setTimeout(function () {
94+
obj._map.featureIndex.inBoundFeatures[0].path.setAttribute("tabindex", 0);
95+
}, 0);
8496
}
85-
} else if (!([9, 16, 13, 27, 49, 50, 51, 52, 53, 54, 55].includes(e.keyCode))){
97+
} else if (!([9, 16, 13, 27, 37, 38, 39, 40, 49, 50, 51, 52, 53, 54, 55].includes(e.keyCode))){
8698
this._map.featureIndex.currentIndex = 0;
8799
this._map.featureIndex.inBoundFeatures[0].path.focus();
88100
}
89101

90102
if(e.target.tagName.toUpperCase() !== "G") return;
91-
if((e.keyCode === 9 || e.keyCode === 16 || e.keyCode === 13 || (e.keyCode >= 49 && e.keyCode <= 55)) && e.type === "keyup") {
103+
if(([9, 13, 16, 37, 38, 39, 40, 49, 50, 51, 52, 53, 54, 55].includes(e.keyCode)) && e.type === "keyup") {
92104
this.openTooltip();
93105
} else if (e.keyCode === 13 || e.keyCode === 32){
94106
this.closeTooltip();

src/mapml/handlers/FeatureIndex.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export var FeatureIndex = L.Handler.extend({
9090
b.dist = Math.sqrt(Math.pow(bc.x - mc.x, 2) + Math.pow(bc.y - mc.y, 2));
9191
return a.dist - b.dist;
9292
});
93-
if(!M.options.featureIndexOverlayOption) this.inBoundFeatures[0].path.setAttribute("tabindex", 0);
93+
this.inBoundFeatures[0].path.setAttribute("tabindex", 0);
9494
},
9595

9696
/**

test/e2e/core/featureLinks.test.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,14 @@ test.describe("Playwright Feature Links Tests", () => {
1515

1616
test.describe("Sub Part Link Tests", () => {
1717
test("Sub-point link adds new layer", async () => {
18-
for(let i = 0; i < 5; i++) {
18+
for(let i = 0; i < 2; i++) {
1919
await page.keyboard.press("Tab");
2020
await page.waitForTimeout(200);
2121
}
22+
for(let i = 0; i < 3; i++) {
23+
await page.keyboard.press("ArrowDown");
24+
await page.waitForTimeout(200);
25+
}
2226
await page.keyboard.press("Enter"); // Press enter on the point subpart of the 'Accessible Square' feature
2327
await page.waitForTimeout(1000);
2428
const layers = await page.$eval(
@@ -33,8 +37,9 @@ test.describe("Playwright Feature Links Tests", () => {
3337
await page.click("div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(2) > div:nth-child(1) > div > button:nth-child(1)");
3438
await page.waitForTimeout(850);
3539
await page.click("body > map");
36-
for(let i = 0; i < 10; i++) {
37-
await page.keyboard.press("Tab");
40+
await page.keyboard.press("Tab");
41+
for(let i = 0; i < 9; i++) {
42+
await page.keyboard.press("ArrowDown");
3843
await page.waitForTimeout(200);
3944
}
4045
const extentBeforeLink = await page.$eval(
@@ -68,8 +73,9 @@ test.describe("Playwright Feature Links Tests", () => {
6873
await page.click("div > div.leaflet-control-container > div.leaflet-top.leaflet-right > div > section > div.leaflet-control-layers-overlays > fieldset:nth-child(2) > div:nth-child(1) > div > button:nth-child(1)");
6974
await page.waitForTimeout(850);
7075
await page.click("body > map");
71-
for(let i = 0; i < 9; i++) {
72-
await page.keyboard.press("Tab");
76+
await page.keyboard.press("Tab");
77+
for(let i = 0; i < 8; i++) {
78+
await page.keyboard.press("ArrowDown");
7379
await page.waitForTimeout(200);
7480
}
7581
await page.keyboard.press("Enter"); // Press enter on main part of 'Inplace' feature

test/e2e/core/tabFeatureNavigation.test.js renamed to test/e2e/core/featureNavigation.test.js

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ test.describe("Playwright Keyboard Navigation + Query Layer Tests" , () => {
66
test.beforeAll(async () => {
77
context = await chromium.launchPersistentContext('');
88
page = context.pages().find((page) => page.url() === 'about:blank') || await context.newPage();
9-
await page.goto("tabFeatureNavigation.html");
9+
await page.goto("featureNavigation.html");
1010
});
1111

1212
test.afterAll(async function () {
1313
await context.close();
1414
});
1515

16-
test.describe("Tab Navigable Tests", () => {
16+
test.describe("Arrow Key Navigable Tests", () => {
1717
test("Tab focuses inline features", async () => {
1818
await page.click("body");
1919
await page.keyboard.press("Tab");
@@ -29,7 +29,7 @@ test.describe("Playwright Keyboard Navigation + Query Layer Tests" , () => {
2929
let tooltipCount = await page.$eval("mapml-viewer .leaflet-tooltip-pane", div => div.childElementCount);
3030
expect(tooltipCount).toEqual(1);
3131

32-
await page.keyboard.press("Tab");
32+
await page.keyboard.press("ArrowDown");
3333
await page.waitForTimeout(500);
3434
const aHandleNext = await page.evaluateHandle(() => document.querySelector("mapml-viewer"));
3535
const nextHandleNext = await page.evaluateHandle(doc => doc.shadowRoot, aHandleNext);
@@ -48,19 +48,53 @@ test.describe("Playwright Keyboard Navigation + Query Layer Tests" , () => {
4848
await page.keyboard.press("Tab"); // focus the map
4949

5050
await page.keyboard.press("Tab"); // Vermont (features are sorted by distance from map centre)
51-
await page.keyboard.press("Tab"); // New York
52-
await page.keyboard.press("Tab"); // New Hampshire
53-
await page.keyboard.press("Tab"); // Massachusetts
51+
await page.keyboard.press("ArrowDown"); // New York
52+
await page.keyboard.press("ArrowDown"); // New Hampshire
53+
await page.keyboard.press("ArrowDown"); // Massachusetts
5454
const aHandle = await page.evaluateHandle(() => document.querySelector("mapml-viewer"));
5555
const nextHandle = await page.evaluateHandle(doc => doc.shadowRoot, aHandle);
5656
const focused = await page.evaluate(root => root.activeElement.getAttribute("aria-label").trim(), nextHandle);
5757
expect(focused).toEqual("Massachusetts");
5858

59-
await page.keyboard.press("Tab");
59+
await page.keyboard.press("ArrowDown");
6060
const focusedNext = await page.evaluate(root => root.activeElement.getAttribute("aria-label").trim(), nextHandle);
6161

6262
expect(focusedNext).toEqual("Connecticut"); // spelling error https://en.wikipedia.org/wiki/Caractacus_Pott
6363
});
64+
65+
test("When at last feature, arrow down goes to first feature", async () => {
66+
await page.goto("mapMLTemplatedFeaturesFilter.html");
67+
await page.waitForTimeout(1000);
68+
69+
await page.keyboard.press("Tab");
70+
await page.keyboard.press("Tab");
71+
72+
for(let i = 0; i < 5; i++) {
73+
await page.keyboard.press("ArrowDown");
74+
}
75+
76+
const activeFeature = await page.$eval("body > mapml-viewer",
77+
(map) => map.shadowRoot.activeElement.getAttribute("aria-label"));
78+
79+
await page.keyboard.press("ArrowDown");
80+
await page.waitForTimeout(500);
81+
const nextActiveFeature = await page.$eval("body > mapml-viewer",
82+
(map) => map.shadowRoot.activeElement.getAttribute("aria-label"));
83+
84+
await expect(activeFeature).toEqual("Hung Sum Restaurant");
85+
await expect(nextActiveFeature).toEqual("Sushi 88");
86+
});
87+
88+
test("Center feature is always refocused", async () => {
89+
await page.keyboard.press("ArrowDown");
90+
await page.keyboard.press("ArrowDown");
91+
await page.keyboard.press("Shift+Tab");
92+
await page.keyboard.press("Tab");
93+
94+
const activeFeature = await page.$eval("body > mapml-viewer",
95+
(map) => map.shadowRoot.activeElement.getAttribute("aria-label"));
96+
await expect(activeFeature).toEqual("Sushi 88");
97+
});
6498
});
6599

66100
});

test/e2e/core/linkTypes.test.js

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ test.describe("Playwright Feature Links Tests", () => {
1616
test.describe("HTML Link Type Tests", () => {
1717
test("HTML _self target navigates to new page", async () => {
1818
await page.click("body > map");
19-
for(let i = 0; i < 9; i++) {
20-
await page.keyboard.press("Tab");
19+
await page.keyboard.press("Tab");
20+
await page.waitForTimeout(200);
21+
for(let i = 0; i < 8; i++) {
22+
await page.keyboard.press("ArrowDown");
2123
await page.waitForTimeout(200);
2224
}
2325
await page.keyboard.press("Enter"); // Press enter on the feature in the top-left
@@ -29,10 +31,10 @@ test.describe("Playwright Feature Links Tests", () => {
2931
await page.goBack();
3032
await page.waitForTimeout(1000);
3133
await page.click("body > map");
32-
for(let i = 0; i < 2; i++) {
33-
await page.keyboard.press("Tab");
34-
await page.waitForTimeout(200);
35-
}
34+
await page.keyboard.press("Tab");
35+
await page.waitForTimeout(200);
36+
await page.keyboard.press("ArrowDown");
37+
await page.waitForTimeout(200);
3638
await page.keyboard.press("Enter"); // Press enter on the top point feature in the top left
3739
await page.waitForTimeout(1000);
3840
const url = await page.url();
@@ -42,8 +44,10 @@ test.describe("Playwright Feature Links Tests", () => {
4244
await page.goBack();
4345
await page.waitForTimeout(1000);
4446
await page.click("body > map");
45-
for(let i = 0; i < 9; i++) {
46-
await page.keyboard.press("Tab");
47+
await page.keyboard.press("Tab");
48+
await page.waitForTimeout(200);
49+
for(let i = 0; i < 8; i++) {
50+
await page.keyboard.press("ArrowDown");
4751
await page.waitForTimeout(200);
4852
}
4953
await page.keyboard.press("Enter"); // Press enter on the second feature in the top left
@@ -55,10 +59,8 @@ test.describe("Playwright Feature Links Tests", () => {
5559
await page.goBack();
5660
await page.waitForTimeout(1000);
5761
await page.click("body > map");
58-
for(let i = 0; i < 1; i++) {
59-
await page.keyboard.press("Tab");
60-
await page.waitForTimeout(200);
61-
}
62+
await page.keyboard.press("Tab");
63+
await page.waitForTimeout(200);
6264
await page.keyboard.press("Enter"); // Press enter on the second point in the top left
6365
await page.waitForTimeout(1000);
6466
const extent = await page.$eval(

test/e2e/core/popupTabNavigation.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ test.describe("Playwright Keyboard Navigation + Query Layer Tests" , () => {
106106
});
107107

108108
test("Previous feature button focuses previous feature", async () => {
109-
await page.keyboard.press("Tab"); // focus next feature
109+
await page.keyboard.press("ArrowDown"); // focus next feature
110110
await page.waitForTimeout(500);
111111
await page.keyboard.press("Enter"); // popup
112112
await page.waitForTimeout(500);

0 commit comments

Comments
 (0)