Skip to content

Commit

Permalink
feat(cdk)!: add configuration to tuiGetClosestFocusableElement (#2436)
Browse files Browse the repository at this point in the history
  • Loading branch information
splincode committed Aug 24, 2022
1 parent 6ec3699 commit f40f9ba
Show file tree
Hide file tree
Showing 13 changed files with 140 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export class TuiToolbarNavigationManagerDirective {
for (const el of tools) {
const focusableElement = tuiIsNativeMouseFocusable(el)
? el
: tuiGetClosestFocusable(el, el, false, false);
: tuiGetClosestFocusable({initial: el, root: el, keyboard: false});

if (focusableElement) {
return focusableElement;
Expand All @@ -65,22 +65,30 @@ export class TuiToolbarNavigationManagerDirective {
return wrapper;
}

const lookedInside = tuiGetClosestFocusable(wrapper, wrapper, false, false);
const lookedInside = tuiGetClosestFocusable({
initial: wrapper,
root: wrapper,
keyboard: false,
});

return (
lookedInside ||
tuiGetClosestFocusable(wrapper, this.elementRef.nativeElement, true, false)
tuiGetClosestFocusable({
initial: wrapper,
root: this.elementRef.nativeElement,
previous: true,
keyboard: false,
})
);
}

private findNextTool(wrapper: HTMLElement): HTMLElement | null {
return tuiIsNativeMouseFocusable(wrapper)
? wrapper
: tuiGetClosestFocusable(
wrapper,
this.elementRef.nativeElement,
false,
false,
);
: tuiGetClosestFocusable({
initial: wrapper,
root: this.elementRef.nativeElement,
keyboard: false,
});
}
}
5 changes: 4 additions & 1 deletion projects/cdk/directives/focus-trap/focus-trap.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ export class TuiFocusTrapDirective implements OnDestroy {
return;
}

const focusable = tuiGetClosestFocusable(nativeElement, nativeElement);
const focusable = tuiGetClosestFocusable({
initial: nativeElement,
root: nativeElement,
});

if (focusable) {
focusable.focus();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ export const DEPRECATED_FUNCTIONS: readonly TypeToRename[] = [
},
{
from: 'getClosestKeyboardFocusable',
to: 'tuiGetClosestKeyboardFocusable',
to: 'tuiGetClosestFocusable',
moduleSpecifier: ['@taiga-ui/cdk'],
},
{
Expand Down
22 changes: 21 additions & 1 deletion projects/cdk/schematics/ng-update/steps/replace-functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ export function replaceFunctions() {
replaceFallbackValue(getNamedImportReferences('fallbackValue', '@taiga-ui/cdk'));
replaceCustomEvent(getNamedImportReferences('tuiCustomEvent', '@taiga-ui/cdk'));
replaceClosestElement(getNamedImportReferences('getClosestElement', '@taiga-ui/cdk'));
modifyFormatNumberArgs();
replaceDeprecatedFunction();
modifyFormatNumberArgs();
modifyClosestFocusable();

successLog(`${SMALL_TAB_SYMBOL}${SUCCESS_SYMBOL} functions replaced \n`);
}
Expand Down Expand Up @@ -136,3 +137,22 @@ function modifyFormatNumberArgs(): void {
}
});
}

function modifyClosestFocusable(): void {
getNamedImportReferences('tuiGetClosestFocusable', '@taiga-ui/cdk')
.map(ref => ref.getParent())
.filter(Node.isCallExpression)
.forEach(fn => {
const args = fn.getArguments();

if (args.length > 1) {
const [initial, prev = false, root, keyboard = true] = args.map(arg =>
arg.getText(),
);

fn.replaceWithText(
`tuiGetClosestFocusable({initial: ${initial}, root: ${root}, previous: ${prev}, keyboard: ${keyboard}})`,
);
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class AppComponent extends AbstractTuiController {
control = new FormControl('', [Validators.nullValidator]);
onMouseDown(event: MouseEvent, target: HTMLElement) {
if (tuiGetClosestFocusable(target, 'button')) {
if (tuiGetClosestFocusable({initial: target, root: 'button', previous: false, keyboard: true})) {
return null;
}
}
Expand Down Expand Up @@ -87,7 +87,7 @@ export class AppComponent extends TuiController {
control = new FormControl('', [EMPTY_VALIDATOR]);
onMouseDown(event: MouseEvent, target: HTMLElement) {
if (tuiGetClosestFocusable(target, 'button')) {
if (tuiGetClosestFocusable(target, false, 'button')) {
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {createAngularJson} from '../../utils/create-angular-json';
const collectionPath = join(__dirname, '../../migration.json');

const AFTER = `import {Component} from '@angular/core';
import { tuiGetClosestFocusable } from '@taiga-ui/cdk';
import {
TUI_NUMBER_FORMAT,
tuiFormatNumber,
Expand All @@ -26,6 +27,11 @@ tuiFormatNumber(123.45, {decimalLimit: 3, decimalSeparator: '.', thousandSeparat
tuiFormatNumber(12345.67, {decimalLimit: 4, decimalSeparator: ',', thousandSeparator: '.', zeroPadding: true});
tuiFormatNumber(27, {decimalLimit: 5, decimalSeparator: ',', thousandSeparator: '.', zeroPadding: false});
tuiGetClosestFocusable({initial: el, root: el, previous: false, keyboard: false});
tuiGetClosestFocusable({initial: host, root: root, previous: previous, keyboard: true});
tuiGetClosestFocusable({initial: button, root: wrapper, previous: prev, keyboard: true});
tuiGetClosestFocusable({initial: host, root: host, previous: true, keyboard: true});
const dynamicDecimalLimit = Math.random() > 0.5;
const decimalSeparatorVariable = ',';
const thousandSeparatorVariable = '_';
Expand All @@ -49,6 +55,13 @@ export class AppComponent extends AbstractTuiController {
return String(this.day).padStart(2, '0');
}
test(): void {
tuiGetClosestFocusable({initial: this.host, root: this.elementRef.nativeElement, previous: false, keyboard: true});
tuiGetClosestFocusable({initial: wrapper, root: this.elementRef.nativeElement, previous: true, keyboard: false});
const focusable = tuiGetClosestFocusable({initial: this.elementRef.nativeElement, root: this.elementRef.nativeElement, previous: false, keyboard: true});
const focusable = tuiGetClosestFocusable({initial: initial, root: this.wrapper.nativeElement, previous: !first, keyboard: true});
}
constructor(@Inject(TUI_NUMBER_FORMAT) private readonly numberFormat: TuiNumberFormatSettings) {}
private hasClosest(suitableNode: any, selector: string): void {
Expand All @@ -67,7 +80,7 @@ const event = new CustomEvent("hello", {
`;

const BEFORE = `import {Component} from '@angular/core';
import { fallbackValue, tuiCustomEvent, getClosestElement, padStart } from '@taiga-ui/cdk';
import { fallbackValue, tuiCustomEvent, getClosestElement, padStart, tuiGetClosestFocusable } from '@taiga-ui/cdk';
import {
TUI_NUMBER_FORMAT,
tuiFormatNumber,
Expand All @@ -80,6 +93,11 @@ tuiFormatNumber(123.45, 3, '.');
tuiFormatNumber(12345.67, 4, ',', '.');
tuiFormatNumber(27, 5, ',', '.', false);
tuiGetClosestFocusable(el, false, el, false);
tuiGetClosestFocusable(host, previous, root);
tuiGetClosestFocusable(button, prev, wrapper);
tuiGetClosestFocusable(host, true, host, true);
const dynamicDecimalLimit = Math.random() > 0.5;
const decimalSeparatorVariable = ',';
const thousandSeparatorVariable = '_';
Expand Down Expand Up @@ -108,6 +126,21 @@ export class AppComponent extends AbstractTuiController {
return padStart(String(this.day), 2, '0');
}
test(): void {
tuiGetClosestFocusable(this.host, false, this.elementRef.nativeElement);
tuiGetClosestFocusable(wrapper, true, this.elementRef.nativeElement, false);
const focusable = tuiGetClosestFocusable(
this.elementRef.nativeElement,
false,
this.elementRef.nativeElement,
);
const focusable = tuiGetClosestFocusable(
initial,
!first,
this.wrapper.nativeElement,
);
}
constructor(@Inject(TUI_NUMBER_FORMAT) private readonly numberFormat: TuiNumberFormatSettings) {}
private hasClosest(suitableNode: any, selector: string): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,42 @@ import {tuiIsHTMLElement} from '@taiga-ui/cdk/utils/dom';
import {tuiIsNativeKeyboardFocusable} from './is-native-keyboard-focusable';
import {tuiIsNativeMouseFocusable} from './is-native-mouse-focusable';

export interface TuiGetClosestFocusableOptions {
/**
* @description:
* current HTML element
*/
initial: Element;

/**
* @description:
* top Node limiting the search area
*/
root: Node;

/**
* @description:
* should it look backwards instead (find item that will be focused with Shift + Tab)
*/
previous?: boolean;

/**
* @description:
* determine if only keyboard focus is of interest
*/
keyboard?: boolean;
}

/**
* @description:
* Finds the closest element that can be focused with a keyboard or mouse in theory
*
* @param initial current HTML element
* @param prev should it look backwards instead (find item that will be focused with Shift + Tab)
* @param root top Node limiting the search area
* @param keyboard determine if only keyboard focus is of interest
*
*/
export function tuiGetClosestFocusable(
initial: Element,
root: Node,
prev: boolean = false,
keyboard: boolean = true,
): HTMLElement | null {
export function tuiGetClosestFocusable({
initial,
root,
previous = false,
keyboard = true,
}: TuiGetClosestFocusableOptions): HTMLElement | null {
if (!root.ownerDocument) {
return null;
}
Expand All @@ -32,12 +53,12 @@ export function tuiGetClosestFocusable(

treeWalker.currentNode = initial;

while (prev ? treeWalker.previousNode() : treeWalker.nextNode()) {
while (previous ? treeWalker.previousNode() : treeWalker.nextNode()) {
if (tuiIsHTMLElement(treeWalker.currentNode)) {
initial = treeWalker.currentNode;
}

if (check(initial) && tuiIsHTMLElement(initial)) {
if (tuiIsHTMLElement(initial) && check(initial)) {
return initial;
}
}
Expand Down
2 changes: 1 addition & 1 deletion projects/cdk/utils/focus/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './blur-native-focused';
export * from './get-closest-keyboard-focusable';
export * from './get-closest-focusable';
export * from './get-native-focused';
export * from './is-native-focused';
export * from './is-native-focused-in';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {tuiGetClosestFocusable} from '../get-closest-keyboard-focusable';
import {tuiGetClosestFocusable} from '@taiga-ui/cdk';

describe(`getClosestKeyboardFocusable`, () => {
describe(`tuiGetClosestFocusable`, () => {
it(`returns null if root has no document`, () => {
const root: Node = {} as unknown as Node;
const divElement = document.createElement(`div`);

expect(tuiGetClosestFocusable(divElement, root)).toBe(null);
expect(tuiGetClosestFocusable({initial: divElement, root})).toBe(null);
});

it(`returns closest focusable going backwards`, () => {
Expand All @@ -17,7 +17,9 @@ describe(`getClosestKeyboardFocusable`, () => {
root.appendChild(divElement);
document.body.appendChild(root);

expect(tuiGetClosestFocusable(divElement, root, true)).toBe(buttonElement);
expect(tuiGetClosestFocusable({initial: divElement, root, previous: true})).toBe(
buttonElement,
);

document.body.removeChild(root);
});
Expand All @@ -31,7 +33,7 @@ describe(`getClosestKeyboardFocusable`, () => {
root.appendChild(buttonElement);
document.body.appendChild(root);

expect(tuiGetClosestFocusable(divElement, root)).toBe(buttonElement);
expect(tuiGetClosestFocusable({initial: divElement, root})).toBe(buttonElement);

document.body.removeChild(root);
});
Expand All @@ -43,7 +45,7 @@ describe(`getClosestKeyboardFocusable`, () => {
root.appendChild(divElement);
document.body.appendChild(root);

expect(tuiGetClosestFocusable(divElement, root)).toBe(null);
expect(tuiGetClosestFocusable({initial: divElement, root})).toBe(null);

document.body.removeChild(root);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ export class TuiHostedDropdownComponent implements TuiFocusableElementAccessor {
get nativeFocusableElement(): TuiNativeFocusableElement | null {
return tuiIsNativeKeyboardFocusable(this.host)
? this.host
: tuiGetClosestFocusable(this.host, this.elementRef.nativeElement);
: tuiGetClosestFocusable({
initial: this.host,
root: this.elementRef.nativeElement,
});
}

@HostBinding(`class._hosted_dropdown_focused`)
Expand Down Expand Up @@ -225,11 +228,11 @@ export class TuiHostedDropdownComponent implements TuiFocusableElementAccessor {
const initial = first
? this.wrapper.nativeElement
: this.wrapper.nativeElement.nextElementSibling;
const focusable = tuiGetClosestFocusable(
const focusable = tuiGetClosestFocusable({
initial,
this.wrapper.nativeElement,
!first,
);
root: this.wrapper.nativeElement,
previous: !first,
});

if (!focusable) {
return;
Expand Down
4 changes: 2 additions & 2 deletions projects/core/directives/dropdown/dropdown.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,10 +127,10 @@ export class TuiDropdownComponent {
const host = document.createElement(`div`);
const {ownerDocument} = host;
const root = ownerDocument ? ownerDocument.body : host;
let focusable = tuiGetClosestFocusable(host, root, previous);
let focusable = tuiGetClosestFocusable({initial: host, root, previous});

while (focusable !== null && host.contains(focusable)) {
focusable = tuiGetClosestFocusable(focusable, root, previous);
focusable = tuiGetClosestFocusable({initial: focusable, root, previous});
}

focusable?.focus();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,9 @@ export class TuiTabsWithMoreComponent implements AfterViewInit {
}
}

onWrapperArrow(event: Event, wrapper: HTMLElement, prev: boolean): void {
onWrapperArrow(event: Event, wrapper: HTMLElement, previous: boolean): void {
const button: HTMLButtonElement = event.target as HTMLButtonElement;
const target = tuiGetClosestFocusable(button, wrapper, prev);
const target = tuiGetClosestFocusable({initial: button, root: wrapper, previous});

if (target) {
target.focus();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,11 @@ export class TuiDataListDropdownManagerDirective implements AfterViewInit {
}

// First item is focus trap
const focusTrap = tuiGetClosestFocusable(content, content);
const item = tuiGetClosestFocusable(focusTrap || content, content);
const focusTrap = tuiGetClosestFocusable({initial: content, root: content});
const item = tuiGetClosestFocusable({
initial: focusTrap || content,
root: content,
});

if (item) {
item.focus();
Expand Down

0 comments on commit f40f9ba

Please sign in to comment.