Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
zarend committed Nov 16, 2023
1 parent 65ec485 commit e9cc395
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 50 deletions.
8 changes: 0 additions & 8 deletions src/cdk/a11y/key-manager/tree-key-manager-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,6 @@ export interface TreeKeyManagerStrategy<T extends TreeKeyManagerItem> {
/** The currently active item. */
getActiveItem(): T | null;

/**
* Called the first time the Tree component is focused. This method will only be called once over
* the lifetime of the Tree component.
*
* Intended to be used to focus the first item in the tree.
*/
onInitialFocus(): void;

/**
* Focus the provided item by index.
*
Expand Down
15 changes: 12 additions & 3 deletions src/cdk/a11y/key-manager/tree-key-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana

this._setTypeAhead(typeAheadInterval);
}

this._focusInitialNode();
}

/** Stream that emits any time the focused item changes. */
Expand Down Expand Up @@ -225,6 +227,8 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
focusItem(item: T, options?: {emitChangeEvent?: boolean}): void;
focusItem(itemOrIndex: number | T, options?: {emitChangeEvent?: boolean}): void;
focusItem(itemOrIndex: number | T, options: {emitChangeEvent?: boolean} = {}) {
console.log('TreeKeyManager', 'focusItem', itemOrIndex);

// Set default options
options.emitChangeEvent ??= true;

Expand All @@ -250,6 +254,7 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana

if (options.emitChangeEvent) {
// Emit to `change` stream as required by TreeKeyManagerStrategy interface.
console.log('emitting change event');
this.change.next(this._activeItem);
}
this._activeItem?.focus();
Expand Down Expand Up @@ -281,9 +286,7 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
this._items.length &&
this._items.some(item => typeof item.getLabel !== 'function')
) {
throw new Error(
'TreeKeyManager items in typeahead mode must implement the `getLabel` method.',
);
console.error('TreeKeyManager items in typeahead mode must implement the `getLabel` method.');
}

// Debounce the presses of non-navigational keys, collect the ones that correspond to letters
Expand Down Expand Up @@ -417,6 +420,12 @@ export class TreeKeyManager<T extends TreeKeyManagerItem> implements TreeKeyMana
private _activateCurrentItem() {
this._activeItem?.activate();
}

private _focusInitialNode() {
console.log('TreeKeyManager', '_focusInitialNode', `this._items.length:${this._items.length}`);
// TODO: handle when all the items are disabled
this._focusFirstItem();
}
}

/** @docs-private */
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/tree/toggle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ export class CdkTreeNodeToggle<T, K = T> {
? this._tree.toggleDescendants(this._treeNode.data)
: this._tree.toggle(this._treeNode.data);

this._tree._keyManager.focusItem(this._treeNode);
this._tree._keyManager?.focusItem(this._treeNode);
}
}
69 changes: 32 additions & 37 deletions src/cdk/tree/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ type RenderingData<T> =
'class': 'cdk-tree',
'role': 'tree',
'(keydown)': '_sendKeydownToKeyManager($event)',
'(focus)': '_focusInitialTreeItem()',
},
encapsulation: ViewEncapsulation.None,

Expand Down Expand Up @@ -259,22 +258,26 @@ export class CdkTree<T, K = T>
private _keyManagerFactory = inject(TREE_KEY_MANAGER) as TreeKeyManagerFactory<CdkTreeNode<T, K>>;

/** The key manager for this tree. Handles focus and activation based on user keyboard input. */
_keyManager: TreeKeyManagerStrategy<CdkTreeNode<T, K>>;
_keyManager: TreeKeyManagerStrategy<CdkTreeNode<T, K>> | undefined;

constructor(
private _differs: IterableDiffers,
private _changeDetectorRef: ChangeDetectorRef,
private _dir: Directionality,
private _elementRef: ElementRef<HTMLElement>,
) {}
) {
console.log('CdkTree', 'constructor');
}

ngAfterContentInit() {
this._initializeKeyManager();
console.log('CdkTree', 'ngAfterContentInit');
}

ngAfterContentChecked() {
console.log('CdkTree', 'ngAfterContentChecked');
this._updateDefaultNodeDefinition();
this._subscribeToDataChanges();
this._initializeKeyManager();
}

ngOnDestroy() {
Expand All @@ -295,6 +298,7 @@ export class CdkTree<T, K = T>
}

ngOnInit() {
console.log('CdkTree', 'ngOnInit');
this._checkTreeControlUsage();
this._initializeDataDiffer();
}
Expand All @@ -319,23 +323,6 @@ export class CdkTree<T, K = T>
}
}

/**
* Sets the tabIndex on the host element.
*
* NB: we don't set this as a host binding since children being activated
* (e.g. on user click) doesn't trigger this component's change detection.
*/
_setTabIndex() {
// If the `TreeKeyManager` has no active item, then we know that we need to focus the initial
// item when the tree is focused. We set the tabindex to be `0` so that we can capture
// the focus event and redirect it. Otherwise, we unset it.
if (!this._keyManager.getActiveItem()) {
this._elementRef.nativeElement.setAttribute('tabindex', '0');
} else {
this._elementRef.nativeElement.removeAttribute('tabindex');
}
}

/**
* Switch to the provided data source by resetting the data and unsubscribing from the current
* render change subscription if one exists. If the data source is null, interpret this by
Expand Down Expand Up @@ -425,6 +412,9 @@ export class CdkTree<T, K = T>
}

private _renderDataChanges(data: RenderingData<T>) {
console.log(
`renderDataChanges nodeType:${data.nodeType} renderNodes:${data.renderNodes.length}`,
);
if (data.nodeType === null) {
this._renderNodeChanges(data.renderNodes);
return;
Expand Down Expand Up @@ -454,6 +444,10 @@ export class CdkTree<T, K = T>
}

private _initializeKeyManager() {
if (this._keyManager) {
return;
}

const items = combineLatest([this._keyManagerNodes, this._nodes]).pipe(
map(([keyManagerNodes, renderNodes]) =>
keyManagerNodes.reduce<CdkTreeNode<T, K>[]>((items, data) => {
Expand All @@ -478,14 +472,10 @@ export class CdkTree<T, K = T>
this._keyManager.change
.pipe(startWith(null), pairwise(), takeUntil(this._onDestroy))
.subscribe(([prev, next]) => {
console.log('keyManage change subscription', prev, next);
prev?._setTabUnfocusable();
next?._setTabFocusable();
});

this._keyManager.change.pipe(startWith(null), takeUntil(this._onDestroy)).subscribe(() => {
// refresh the tabindex when the active item changes.
this._setTabIndex();
});
}

private _initializeDataDiffer() {
Expand Down Expand Up @@ -861,15 +851,7 @@ export class CdkTree<T, K = T>

/** `keydown` event handler; this just passes the event to the `TreeKeyManager`. */
_sendKeydownToKeyManager(event: KeyboardEvent) {
this._keyManager.onKeydown(event);
}

/** `focus` event handler; this focuses the initial item if there isn't already one available. */
_focusInitialTreeItem() {
if (this._keyManager.getActiveItem()) {
return;
}
this._keyManager.onInitialFocus();
this._keyManager?.onKeydown(event);
}

/** Gets all nested descendants of a given node. */
Expand Down Expand Up @@ -1140,7 +1122,7 @@ export class CdkTree<T, K = T>
'[attr.aria-level]': 'level + 1',
'[attr.aria-posinset]': '_getPositionInSet()',
'[attr.aria-setsize]': '_getSetSize()',
'tabindex': '-1',
'[tabindex]': '_getTabindex()',
'role': 'treeitem',
'(click)': '_focusItem()',
'(focus)': '_focusItem()',
Expand Down Expand Up @@ -1282,6 +1264,17 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
return this._tree._getPositionInSet(this._data);
}

protected _getTabindex(): number | null {
if (!this._tree._keyManager) {
return null;
}
// TODO: determine if this equailty check will work in SSR
if (this._tree._keyManager.getActiveItem() === this) {
return 0;
}
return -1;
}

constructor(
protected _elementRef: ElementRef<HTMLElement>,
protected _tree: CdkTree<T, K>,
Expand Down Expand Up @@ -1343,18 +1336,20 @@ export class CdkTreeNode<T, K = T> implements OnDestroy, OnInit, TreeKeyManagerI
}

_setTabFocusable() {
console.log('_setTabFocusable', this._elementRef.nativeElement.textContent);
this._elementRef.nativeElement.setAttribute('tabindex', '0');
}

_setTabUnfocusable() {
console.log('_setTabUnfocusable', this._elementRef.nativeElement.textContent);
this._elementRef.nativeElement.setAttribute('tabindex', '-1');
}

_focusItem() {
if (this.isDisabled) {
return;
}
this._tree._keyManager.focusItem(this);
this._tree._keyManager?.focusItem(this);
}

_emitExpansionState(expanded: boolean) {
Expand Down
5 changes: 4 additions & 1 deletion src/material/tree/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ export class MatTreeNode<T, K = T> extends CdkTreeNode<T, K> implements OnInit,
*/
defaultTabIndex = 0;

protected _getTabindexAttribute() {
protected _getTabindexAttribute(): number | null {
if (!this._tree._keyManager) {
return null;
}
if (isNoopTreeKeyManager(this._tree._keyManager)) {
return this.tabIndex;
}
Expand Down

0 comments on commit e9cc395

Please sign in to comment.