From ce25eb933989420ae89335b9eda82b1e8d2cab80 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Tue, 11 Nov 2025 20:01:06 +0000 Subject: [PATCH 1/2] feat(aria/tree): adds rtl keyboard functionality for tree Updates aria tree keyboard functionality to have RightArrow collapse and LeftArrow expand for rtl. --- src/aria/private/tree/tree.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/aria/private/tree/tree.ts b/src/aria/private/tree/tree.ts index eacc7326cef8..2abfeebaac81 100644 --- a/src/aria/private/tree/tree.ts +++ b/src/aria/private/tree/tree.ts @@ -168,6 +168,9 @@ export interface TreeInputs extends Omit, V>, ' /** The aria-current type. */ currentType: SignalLike<'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'>; + + /** The text direction of the tree. */ + textDirection: SignalLike<'ltr' | 'rtl'>; } export interface TreePattern extends TreeInputs {} @@ -205,12 +208,15 @@ export class TreePattern { /** Whether the tree selection follows focus. */ readonly followFocus = computed(() => this.inputs.selectionMode() === 'follow'); + /** Whether the tree direction is RTL. */ + readonly isRtl = computed(() => this.inputs.textDirection() === 'rtl'); + /** The key for navigating to the previous item. */ readonly prevKey = computed(() => { if (this.inputs.orientation() === 'vertical') { return 'ArrowUp'; } - return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; + return this.isRtl() ? 'ArrowRight' : 'ArrowLeft'; }); /** The key for navigating to the next item. */ @@ -218,7 +224,7 @@ export class TreePattern { if (this.inputs.orientation() === 'vertical') { return 'ArrowDown'; } - return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; + return this.isRtl() ? 'ArrowLeft' : 'ArrowRight'; }); /** The key for collapsing an item or moving to its parent. */ @@ -226,7 +232,7 @@ export class TreePattern { if (this.inputs.orientation() === 'horizontal') { return 'ArrowUp'; } - return this.inputs.textDirection() === 'rtl' ? 'ArrowRight' : 'ArrowLeft'; + return this.isRtl() ? 'ArrowRight' : 'ArrowLeft'; }); /** The key for expanding an item or moving to its first child. */ @@ -234,7 +240,7 @@ export class TreePattern { if (this.inputs.orientation() === 'horizontal') { return 'ArrowDown'; } - return this.inputs.textDirection() === 'rtl' ? 'ArrowLeft' : 'ArrowRight'; + return this.isRtl() ? 'ArrowLeft' : 'ArrowRight'; }); /** Represents the space key. Does nothing when the user is actively using typeahead. */ From ea302c98577013ecffc6ab01de5e25b43d82c9e5 Mon Sep 17 00:00:00 2001 From: Joy Serquina Date: Tue, 11 Nov 2025 20:23:53 +0000 Subject: [PATCH 2/2] feat(aria/tree): adds tree nav rtl example to dev-app Creates tree nav rtl example and adds as a demo to dev-app. --- src/components-examples/aria/tree/BUILD.bazel | 1 + src/components-examples/aria/tree/index.ts | 1 + .../aria/tree/tree-common.css | 8 +++ .../tree-nav-rtl/tree-nav-rtl-example.html | 49 +++++++++++++++++++ .../tree/tree-nav-rtl/tree-nav-rtl-example.ts | 26 ++++++++++ src/dev-app/aria-tree/tree-demo.html | 5 ++ src/dev-app/aria-tree/tree-demo.ts | 2 + 7 files changed, 92 insertions(+) create mode 100644 src/components-examples/aria/tree/tree-nav-rtl/tree-nav-rtl-example.html create mode 100644 src/components-examples/aria/tree/tree-nav-rtl/tree-nav-rtl-example.ts diff --git a/src/components-examples/aria/tree/BUILD.bazel b/src/components-examples/aria/tree/BUILD.bazel index cd934741369b..028e1bb51979 100644 --- a/src/components-examples/aria/tree/BUILD.bazel +++ b/src/components-examples/aria/tree/BUILD.bazel @@ -14,6 +14,7 @@ ng_project( "//:node_modules/@angular/core", "//:node_modules/@angular/forms", "//src/aria/tree", + "//src/cdk/a11y", "//src/material/checkbox", "//src/material/form-field", "//src/material/icon", diff --git a/src/components-examples/aria/tree/index.ts b/src/components-examples/aria/tree/index.ts index d017046af3c0..4ac8ffeefc69 100644 --- a/src/components-examples/aria/tree/index.ts +++ b/src/components-examples/aria/tree/index.ts @@ -6,5 +6,6 @@ export {TreeDisabledSkippedExample} from './tree-disabled-skipped/tree-disabled- export {TreeMultiSelectExample} from './tree-multi-select/tree-multi-select-example'; export {TreeMultiSelectFollowFocusExample} from './tree-multi-select-follow-focus/tree-multi-select-follow-focus-example'; export {TreeNavExample} from './tree-nav/tree-nav-example'; +export {TreeNavRtlExample} from './tree-nav-rtl/tree-nav-rtl-example'; export {TreeSingleSelectExample} from './tree-single-select/tree-single-select-example'; export {TreeSingleSelectFollowFocusExample} from './tree-single-select-follow-focus/tree-single-select-follow-focus-example'; diff --git a/src/components-examples/aria/tree/tree-common.css b/src/components-examples/aria/tree/tree-common.css index 643fb5ab385d..a6ea2a7d3c61 100644 --- a/src/components-examples/aria/tree/tree-common.css +++ b/src/components-examples/aria/tree/tree-common.css @@ -44,6 +44,14 @@ transform: rotate(90deg); } +.example-tree[dir='rtl'] .example-tree-item .example-parent-icon { + transform: scaleX(-1); +} + +.example-tree[dir='rtl'] .example-tree-item[aria-expanded='true'] .example-parent-icon { + transform: scaleX(-1) rotate(90deg); +} + .example-selected-icon { visibility: hidden; margin-left: auto; diff --git a/src/components-examples/aria/tree/tree-nav-rtl/tree-nav-rtl-example.html b/src/components-examples/aria/tree/tree-nav-rtl/tree-nav-rtl-example.html new file mode 100644 index 000000000000..8878e9928073 --- /dev/null +++ b/src/components-examples/aria/tree/tree-nav-rtl/tree-nav-rtl-example.html @@ -0,0 +1,49 @@ +
    + +
+ + + @for (node of nodes; track node.value) { + + + + {{ node.name }} + + + + @if (node.children) { +
    + + + +
+ } + } +
diff --git a/src/components-examples/aria/tree/tree-nav-rtl/tree-nav-rtl-example.ts b/src/components-examples/aria/tree/tree-nav-rtl/tree-nav-rtl-example.ts new file mode 100644 index 000000000000..8e667f57f5e2 --- /dev/null +++ b/src/components-examples/aria/tree/tree-nav-rtl/tree-nav-rtl-example.ts @@ -0,0 +1,26 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {Component} from '@angular/core'; +import {Dir} from '@angular/cdk/bidi'; +import {NgTemplateOutlet} from '@angular/common'; +import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree'; +import {TreeNode, NODES} from '../tree-data'; + +/** + * @title Tree with nav mode. + */ +@Component({ + selector: 'tree-nav-rtl-example', + templateUrl: 'tree-nav-rtl-example.html', + styleUrl: '../tree-common.css', + imports: [Dir, Tree, TreeItem, TreeItemGroup, NgTemplateOutlet], +}) +export class TreeNavRtlExample { + nodes: TreeNode[] = NODES; +} diff --git a/src/dev-app/aria-tree/tree-demo.html b/src/dev-app/aria-tree/tree-demo.html index 3003bb1817ea..3ea45d3f1a11 100644 --- a/src/dev-app/aria-tree/tree-demo.html +++ b/src/dev-app/aria-tree/tree-demo.html @@ -44,6 +44,11 @@

Active Descendant

Nav Mode

+ +
+

Rtl Nav Mode

+ +
diff --git a/src/dev-app/aria-tree/tree-demo.ts b/src/dev-app/aria-tree/tree-demo.ts index 9807633d0653..bdd42762f58c 100644 --- a/src/dev-app/aria-tree/tree-demo.ts +++ b/src/dev-app/aria-tree/tree-demo.ts @@ -18,6 +18,7 @@ import { TreeNavExample, TreeSingleSelectExample, TreeSingleSelectFollowFocusExample, + TreeNavRtlExample, } from '@angular/components-examples/aria/tree'; @Component({ @@ -31,6 +32,7 @@ import { TreeMultiSelectExample, TreeMultiSelectFollowFocusExample, TreeNavExample, + TreeNavRtlExample, TreeSingleSelectExample, TreeSingleSelectFollowFocusExample, ],