Skip to content

Commit

Permalink
fix(tree-view): announce active tree node links as selected
Browse files Browse the repository at this point in the history
Previously, the `aria-selected` attribute and the selected/unselected
screen reader text was only set on selectable tree nodes.

Now, the `aria-selected` attribute and selected screen reader text is
also set on active tree node links so that this state is indicated to
screen reader users.

VPAT-603
  • Loading branch information
kevinbuhmann committed Nov 9, 2022
1 parent 21988ab commit 15caf8e
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 4 deletions.
4 changes: 3 additions & 1 deletion projects/angular/clarity.api.md
Expand Up @@ -4515,10 +4515,12 @@ export class ClrTreeNode<T> implements OnInit, AfterContentInit, OnDestroy {

// @public (undocumented)
export class ClrTreeNodeLink {
constructor(el: ElementRef);
constructor(el: ElementRef<HTMLElement>);
// (undocumented)
activate(): void;
// (undocumented)
get active(): boolean;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<ClrTreeNodeLink, ".clr-treenode-link", never, {}, {}, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<ClrTreeNodeLink, never>;
Expand Down
6 changes: 5 additions & 1 deletion projects/angular/src/data/tree-view/tree-node-link.ts
Expand Up @@ -10,7 +10,11 @@ import { Directive, ElementRef } from '@angular/core';
selector: '.clr-treenode-link',
})
export class ClrTreeNodeLink {
constructor(private el: ElementRef) {}
constructor(private el: ElementRef<HTMLElement>) {}

get active() {
return this.el.nativeElement.classList.contains('active');
}

activate() {
if (this.el.nativeElement && this.el.nativeElement.click) {
Expand Down
2 changes: 1 addition & 1 deletion projects/angular/src/data/tree-view/tree-node.html
Expand Up @@ -48,7 +48,7 @@
</div>
<div class="clr-treenode-content" (mousedown)="focusTreeNode()">
<ng-content></ng-content>
<div class="clr-sr-only" *ngIf="featuresService.selectable">
<div class="clr-sr-only" *ngIf="featuresService.selectable || ariaSelected">
<span *ngIf="ariaSelected"> selected</span>
<span *ngIf="!ariaSelected"> unselected</span>
</div>
Expand Down
40 changes: 40 additions & 0 deletions projects/angular/src/data/tree-view/tree-node.spec.ts
Expand Up @@ -37,6 +37,17 @@ class TestComponent {
expandable: boolean | undefined;
}

@Component({
template: `
<clr-tree-node>
<a href="href" class="clr-treenode-link" [class.active]="active">Hello world</a>
</clr-tree-node>
`,
})
class LinkTestComponent {
active: boolean | undefined;
}

interface TsApiContext {
node: ClrTreeNode<void>;
parent: ClrTreeNode<void>;
Expand Down Expand Up @@ -489,5 +500,34 @@ export default function (): void {
expect(focusManager.focusParent).toHaveBeenCalledWith(this.clarityDirective._model);
});
});

describe('Ally with link nodes', function () {
type Context = TestContext<ClrTreeNode<void>, LinkTestComponent>;

spec(ClrTreeNode, LinkTestComponent, ClrTreeViewModule, {
imports: [NoopAnimationsModule, ClrIconModule],
providers: [TreeFocusManagerService],
});

let contentContainer: HTMLElement;

beforeEach(function () {
contentContainer = this.clarityElement.querySelector('.clr-tree-node-content-container');
});

it('adds the aria-selected attribute and screen reader text for active tree node links', function (this: Context) {
this.hostComponent.active = true;
this.detectChanges();
expect(contentContainer.getAttribute('aria-selected')).toBe('true');
expect(contentContainer.textContent.trim().replace(/\s+/g, ' ')).toBe('Hello world selected');
});

it('does not add the aria-selected attribute or screen reader text for inactive tree node links', function (this: Context) {
this.hostComponent.active = false;
this.detectChanges();
expect(contentContainer.getAttribute('aria-selected')).toBeNull();
expect(contentContainer.textContent.trim()).toBe('Hello world');
});
});
});
}
8 changes: 7 additions & 1 deletion projects/angular/src/data/tree-view/tree-node.ts
Expand Up @@ -142,7 +142,13 @@ export class ClrTreeNode<T> implements OnInit, AfterContentInit, OnDestroy {
@Output('clrSelectedChange') selectedChange = new EventEmitter<ClrSelectedState>(false);

get ariaSelected(): boolean {
return this.isSelectable() ? this._model.selected.value === ClrSelectedState.SELECTED : null;
if (this.isSelectable()) {
return this._model.selected.value === ClrSelectedState.SELECTED;
} else if (this.treeNodeLink?.active) {
return true;
} else {
return null;
}
}

// Allows the consumer to override our logic deciding if a node is expandable.
Expand Down

0 comments on commit 15caf8e

Please sign in to comment.