Skip to content

Commit 463687d

Browse files
authored
fix(a11y): narrate person list position in mgt-agenda (#3221)
* fix person in list narration * fix list item arrow interaction * fix overflow * add tests, bump test helpers * fix narrator
1 parent 1f080d9 commit 463687d

File tree

6 files changed

+104
-14
lines changed

6 files changed

+104
-14
lines changed
Binary file not shown.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@
7676
"@esm-bundle/chai": "^4.3.4-fix.0",
7777
"@microsoft/eslint-config-msgraph": "^3.0.3",
7878
"@open-wc/testing": "^4.0.0",
79-
"@open-wc/testing-helpers": "^3.0.0",
79+
"@open-wc/testing-helpers": "3.0.1",
8080
"@rollup/plugin-babel": "^6.0.4",
8181
"@rollup/plugin-commonjs": "^25.0.7",
8282
"@rollup/plugin-json": "^6.1.0",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* -------------------------------------------------------------------------------------------
3+
* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License.
4+
* See License in the project root for license information.
5+
* -------------------------------------------------------------------------------------------
6+
*/
7+
8+
import { fixture, html, expect, oneEvent } from '@open-wc/testing';
9+
import { MockProvider, Providers } from '@microsoft/mgt-element';
10+
import { MgtPeople, registerMgtPeopleComponent } from './mgt-people';
11+
12+
describe('mgt-people - tests', () => {
13+
registerMgtPeopleComponent();
14+
Providers.globalProvider = new MockProvider(true);
15+
16+
it('should render with overflow', async () => {
17+
const mgtPeople = await fixture(html`<mgt-people
18+
people-queries="Lidia, Megan, Lynne, Brian, Joni">
19+
</mgt-people>
20+
`);
21+
// @ts-expect-error TS2554 expects 3 arguments got 2 https://github.com/open-wc/open-wc/issues/2746
22+
await oneEvent(mgtPeople, 'people-rendered');
23+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
24+
expect(mgtPeople.shadowRoot.querySelector('.overflow')).to.not.be.null;
25+
await expect(mgtPeople).shadowDom.to.be.accessible();
26+
});
27+
28+
it('has required scopes', () => {
29+
expect(MgtPeople.requiredScopes).to.have.members([
30+
'user.readbasic.all',
31+
'user.read.all',
32+
'user.read',
33+
'people.read',
34+
'presence.read.all',
35+
'presence.read',
36+
'contacts.read'
37+
]);
38+
});
39+
});

packages/mgt-components/src/components/mgt-people/mgt-people.ts

Lines changed: 42 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,18 @@ import { getPeople, getPeopleFromResource } from '../../graph/graph.people';
1313
import { getUsersPresenceByPeople } from '../../graph/graph.presence';
1414
import { findGroupMembers, getUsersForPeopleQueries, getUsersForUserIds } from '../../graph/graph.user';
1515
import { IDynamicPerson } from '../../graph/types';
16-
import { Providers, ProviderState, MgtTemplatedTaskComponent, mgtHtml } from '@microsoft/mgt-element';
16+
import {
17+
Providers,
18+
ProviderState,
19+
MgtTemplatedTaskComponent,
20+
registerComponent,
21+
mgtHtml
22+
} from '@microsoft/mgt-element';
1723
import '../../styles/style-helper';
1824
import { type PersonCardInteraction, personCardConverter } from './../PersonCardInteraction';
1925

2026
import { styles } from './mgt-people-css';
2127
import { MgtPerson, registerMgtPersonComponent } from '../mgt-person/mgt-person';
22-
import { registerComponent } from '@microsoft/mgt-element';
2328

2429
/**
2530
* web component to display a group of people or contacts by using their photos or initials.
@@ -249,6 +254,19 @@ export class MgtPeople extends MgtTemplatedTaskComponent {
249254
return this.renderTemplate('default', { people: this.people, max: this.showMax }) || this.renderPeople();
250255
};
251256

257+
protected updated(changedProperties: Map<string | number | symbol, unknown>): void {
258+
super.updated(changedProperties);
259+
this.checkPeopleListAndFireEvent();
260+
}
261+
262+
private checkPeopleListAndFireEvent(): void {
263+
const peopleList = this.shadowRoot?.querySelector('.people-list');
264+
265+
if (peopleList?.childElementCount > 0) {
266+
this.fireCustomEvent('people-rendered');
267+
}
268+
}
269+
252270
/**
253271
* Render the loading state.
254272
*
@@ -280,7 +298,7 @@ export class MgtPeople extends MgtTemplatedTaskComponent {
280298
maxPeople,
281299
p => (p.id ? p.id : p.displayName),
282300
p => html`
283-
<li tabindex="-1" class="people-person">
301+
<li class="people-person">
284302
${this.renderPerson(p)}
285303
</li>
286304
`
@@ -306,7 +324,7 @@ export class MgtPeople extends MgtTemplatedTaskComponent {
306324
people: this.people
307325
}) ||
308326
html`
309-
<li tabindex="-1" aria-label="and ${extra} more attendees" class="overflow"><span>+${extra}</span></li>
327+
<li aria-label="and ${extra} more attendees" class="overflow"><span>+${extra}</span></li>
310328
`
311329
);
312330
}
@@ -320,22 +338,33 @@ export class MgtPeople extends MgtTemplatedTaskComponent {
320338
const peopleContainer: HTMLElement = this.shadowRoot.querySelector('.people-list');
321339
let person: HTMLElement;
322340
const peopleElements: HTMLCollection = peopleContainer?.children;
341+
const keyName = event.key;
323342
// Default all tabindex values in li nodes to -1
324343
for (const element of peopleElements) {
325344
const el: HTMLElement = element as HTMLElement;
326-
el.setAttribute('tabindex', '-1');
345+
el.removeAttribute('tabindex');
346+
person = el?.querySelector('mgt-person');
347+
person = person?.shadowRoot.querySelector('.person-root');
348+
person?.removeAttribute('tabindex');
327349
el.blur();
328350
}
351+
if (event.target === peopleContainer && (keyName === 'Tab' || keyName === 'Escape')) {
352+
this._arrowKeyLocation = -1;
353+
peopleContainer?.blur();
354+
}
329355

330-
const childElementCount = peopleContainer.childElementCount;
331-
const keyName = event.key;
356+
let childElementCount = peopleContainer?.childElementCount;
357+
let overflow = peopleContainer?.querySelector('.overflow');
358+
if (overflow) {
359+
// account for overflow
360+
overflow = overflow as HTMLElement;
361+
overflow.removeAttribute('tabindex');
362+
childElementCount--;
363+
}
332364
if (keyName === 'ArrowRight') {
333365
this._arrowKeyLocation = (this._arrowKeyLocation + 1 + childElementCount) % childElementCount;
334366
} else if (keyName === 'ArrowLeft') {
335367
this._arrowKeyLocation = (this._arrowKeyLocation - 1 + childElementCount) % childElementCount;
336-
} else if (keyName === 'Tab' || keyName === 'Escape') {
337-
this._arrowKeyLocation = -1;
338-
peopleContainer.blur();
339368
} else if (['Enter', 'space', ' '].includes(keyName)) {
340369
if (this.personCardInteraction !== 'none') {
341370
const personEl = peopleElements[this._arrowKeyLocation] as HTMLElement;
@@ -348,8 +377,10 @@ export class MgtPeople extends MgtTemplatedTaskComponent {
348377

349378
if (this._arrowKeyLocation > -1) {
350379
person = peopleElements[this._arrowKeyLocation] as HTMLElement;
351-
person.setAttribute('tabindex', '1');
380+
person.setAttribute('tabindex', '0');
352381
person.focus();
382+
person = person.querySelector('.people-person');
383+
person?.shadowRoot.querySelector('.person-root').setAttribute('tabindex', '0');
353384
}
354385
};
355386

@@ -377,7 +408,6 @@ export class MgtPeople extends MgtTemplatedTaskComponent {
377408
// query the image from the graph
378409
mgtHtml`
379410
<mgt-person
380-
class="people-person"
381411
.personDetails=${person}
382412
.fetchImage=${true}
383413
.avatarSize=${avatarSize}

stories/components/people/people.html.stories.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,13 @@ export const RTL = () => html`
2323
<mgt-people show-max="5"></mgt-people>
2424
</body>
2525
`;
26+
27+
export const Events = () => html`
28+
<mgt-people people-queries="Megan Bowen"></mgt-people>
29+
<script>
30+
const login = document.querySelector('mgt-people');
31+
login.addEventListener('people-rendered', (e) => {
32+
console.log("People rendered");
33+
})
34+
</script>
35+
`;

yarn.lock

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5393,6 +5393,17 @@ __metadata:
53935393
languageName: node
53945394
linkType: hard
53955395

5396+
"@open-wc/testing-helpers@npm:3.0.1":
5397+
version: 3.0.1
5398+
resolution: "@open-wc/testing-helpers@npm:3.0.1"
5399+
dependencies:
5400+
"@open-wc/scoped-elements": "npm:^3.0.2"
5401+
lit: "npm:^2.0.0 || ^3.0.0"
5402+
lit-html: "npm:^2.0.0 || ^3.0.0"
5403+
checksum: 10/8fc42574b26796c34df7628fdbef93479d0f57be87bcfdaea521200ad97faa135145a0ad17a155e16c336303de46fa28318e3320f9d0c200a63d0c26618bf33c
5404+
languageName: node
5405+
linkType: hard
5406+
53965407
"@open-wc/testing-helpers@npm:^1.8.12":
53975408
version: 1.8.12
53985409
resolution: "@open-wc/testing-helpers@npm:1.8.12"
@@ -21077,7 +21088,7 @@ __metadata:
2107721088
"@esm-bundle/chai": "npm:^4.3.4-fix.0"
2107821089
"@microsoft/eslint-config-msgraph": "npm:^3.0.3"
2107921090
"@open-wc/testing": "npm:^4.0.0"
21080-
"@open-wc/testing-helpers": "npm:^3.0.0"
21091+
"@open-wc/testing-helpers": "npm:3.0.1"
2108121092
"@rollup/plugin-babel": "npm:^6.0.4"
2108221093
"@rollup/plugin-commonjs": "npm:^25.0.7"
2108321094
"@rollup/plugin-json": "npm:^6.1.0"

0 commit comments

Comments
 (0)