Skip to content

Commit 708824d

Browse files
nikkimkbennypowers
andauthored
fix(jump-links): keyboard nav for jump-links list (#2413)
* fix(jump-links): keyboard nav for jump-links list * refactor: use slotchange instead of custom event * perf: avoid using decorators * docs: add changeset * perf: remove query decorator * fix: prevent automatic focus * test(jump-links): refactor test --------- Co-authored-by: Benny Powers <[email protected]> Co-authored-by: Benny Powers <[email protected]>
1 parent cfc5913 commit 708824d

File tree

4 files changed

+37
-27
lines changed

4 files changed

+37
-27
lines changed

.changeset/jump-links-arrow-nav.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
"@patternfly/elements": patch
3+
---
4+
`<pf-jump-links>`: improved accessibility for keyboard users

elements/pf-jump-links/pf-jump-links-item.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { html, LitElement } from 'lit';
22
import { customElement } from 'lit/decorators/custom-element.js';
33
import { property } from 'lit/decorators/property.js';
4-
import { query } from 'lit/decorators/query.js';
54

65
import { ifDefined } from 'lit/directives/if-defined.js';
76

@@ -23,10 +22,13 @@ import { observed } from '@patternfly/pfe-core/decorators/observed.js';
2322
export class PfJumpLinksItem extends LitElement {
2423
static readonly styles = [style];
2524

26-
@query('a') link!:HTMLAnchorElement;
25+
static readonly shadowRootOptions: ShadowRootInit = { ...LitElement.shadowRootOptions, delegatesFocus: true };
26+
27+
/** Whether this item is active. */
2728
@observed('activeChanged')
2829
@property({ type: Boolean, reflect: true }) active = false;
2930

31+
/** hypertext reference for this link */
3032
@property({ reflect: true }) href?: string;
3133

3234
#internals = new InternalsController(this, {
@@ -47,10 +49,6 @@ export class PfJumpLinksItem extends LitElement {
4749
`;
4850
}
4951

50-
focus(): void {
51-
this.link.focus();
52-
}
53-
5452
private activeChanged() {
5553
this.#internals.ariaCurrent = this.active ? 'location' : null;
5654
}

elements/pf-jump-links/pf-jump-links.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import { html, LitElement } from 'lit';
22
import { customElement } from 'lit/decorators/custom-element.js';
33
import { property } from 'lit/decorators/property.js';
4-
import { queryAssignedElements } from 'lit/decorators/query-assigned-elements.js';
54

65
import { ScrollSpyController } from '@patternfly/pfe-core/controllers/scroll-spy-controller.js';
76
import { RovingTabindexController } from '@patternfly/pfe-core/controllers/roving-tabindex-controller.js';
87

9-
import './pf-jump-links-item.js';
108
import '@patternfly/elements/pf-icon/pf-icon.js';
119

10+
import './pf-jump-links-item.js';
11+
1212
import style from './pf-jump-links.css';
13-
import { PfJumpLinksItem } from './pf-jump-links-item.js';
1413

1514
/**
1615
* Jump links allow users to navigate to sections within a page.
@@ -64,22 +63,26 @@ import { PfJumpLinksItem } from './pf-jump-links-item.js';
6463
export class PfJumpLinks extends LitElement {
6564
static readonly styles = [style];
6665

67-
@queryAssignedElements() _items!:PfJumpLinksItem[];
68-
66+
/** Whether the element features a disclosure widget around the nav items */
6967
@property({ reflect: true, type: Boolean }) expandable = false;
7068

69+
/** Whether the expandable element's disclosure widget is expanded */
7170
@property({ reflect: true, type: Boolean }) expanded = false;
7271

72+
/** Whether the layout of children is vertical or horizontal. */
7373
@property({ reflect: true, type: Boolean }) vertical = false;
7474

75+
/** Whether to center children. */
7576
@property({ reflect: true, type: Boolean }) centered = false;
7677

78+
/** Offset to add to the scroll position, potentially for a masthead which content scrolls under. */
7779
@property({ type: Number }) offset = 0;
7880

81+
/** Label to add to nav element. */
7982
@property() label?: string;
8083

81-
#items!:PfJumpLinksItem[];
82-
#init = false;
84+
#initialized = false;
85+
8386
#rovingTabindexController = new RovingTabindexController(this);
8487

8588
#spy = new ScrollSpyController(this, {
@@ -106,22 +109,25 @@ export class PfJumpLinks extends LitElement {
106109
<pf-icon icon="chevron-right"></pf-icon>
107110
<span id="label">${this.label}</span>
108111
</summary>
109-
<slot role="listbox" @slotchange="${this.#onSlotchange}"></slot>
112+
<slot role="listbox" @slotchange="${this.#updateItems}"></slot>
110113
</details>` : html`
111114
<span id="label">${this.label}</span>
112-
<slot role="listbox" @slotchange="${this.#onSlotchange}"></slot>`}
115+
<slot role="listbox" @slotchange="${this.#updateItems}"></slot>`}
113116
</nav>
114117
`;
115118
}
116119

117-
#onSlotchange() {
118-
this.#items = this._items;
119-
const items = this.#items?.map(item=>item.link);
120-
if (this.#init) {
120+
#updateItems() {
121+
const items = Array.from(this.querySelectorAll(':is(pf-jump-links-item, pf-jump-links-list)'))
122+
.flatMap(i => [
123+
...i.shadowRoot?.querySelectorAll('a') ?? [],
124+
...i.querySelectorAll('a') ?? [],
125+
]);
126+
if (this.#initialized) {
121127
this.#rovingTabindexController.updateItems(items);
122128
} else {
123129
this.#rovingTabindexController.initItems(items);
124-
this.#init = true;
130+
this.#initialized = true;
125131
}
126132
}
127133

elements/pf-jump-links/test/pf-jump-links.spec.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ReactiveElement } from 'lit';
2-
import { expect, html, nextFrame } from '@open-wc/testing';
2+
import { expect, html, nextFrame, aTimeout } from '@open-wc/testing';
33
import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js';
44
import { sendKeys } from '@web/test-runner-commands';
55

@@ -22,29 +22,31 @@ async function allUpdates(element: ReactiveElement) {
2222

2323
describe('<pf-jump-links>', function() {
2424
let element: PfJumpLinks;
25+
let firstItem: PfJumpLinksItem;
26+
let secondItem: PfJumpLinksItem;
27+
let thirdItem: PfJumpLinksItem;
2528

2629
beforeEach(async function() {
2730
element = await createFixture<PfJumpLinks>(html`
2831
<pf-jump-links>
2932
<pf-jump-links-item id="first">Inactive section</pf-jump-links-item>
30-
<pf-jump-links-item id="second">Active section</pf-jump-links-item>
33+
<pf-jump-links-item id="second" active>Active section</pf-jump-links-item>
3134
<pf-jump-links-item id="third">Inactive section</pf-jump-links-item>
3235
</pf-jump-links>
3336
`);
3437
await allUpdates(element);
38+
[firstItem, secondItem, thirdItem] = element.querySelectorAll<PfJumpLinksItem>('pf-jump-links-item');
3539
});
3640

3741
describe('tabbing to first item', function() {
38-
let firstItem: PfJumpLinksItem;
39-
let secondItem: PfJumpLinksItem;
4042
let initialActiveElement: Element|null;
4143
beforeEach(async function() {
42-
[firstItem, secondItem] = element.querySelectorAll<PfJumpLinksItem>('pf-jump-links-item');
43-
await sendKeys({ press: 'Tab' });
4444
initialActiveElement = document.activeElement;
45+
await sendKeys({ press: 'Tab' });
46+
await nextFrame();
4547
});
4648

47-
it('should focus a this first jump-links-item', function() {
49+
it('should focus the first jump-links-item', function() {
4850
expect(document.activeElement).to.equal(firstItem);
4951
});
5052

0 commit comments

Comments
 (0)