Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(layout): Navigation simplify 2nd level aside #8141

Merged
merged 13 commits into from
Jul 19, 2024
29 changes: 15 additions & 14 deletions projects/core/components/data-list/data-list.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {AsyncPipe, NgIf} from '@angular/common';
import type {QueryList} from '@angular/core';
import {NgIf} from '@angular/common';
import type {AfterContentChecked, QueryList} from '@angular/core';
import {
ChangeDetectionStrategy,
Component,
Expand All @@ -11,17 +11,15 @@ import {
Input,
ViewEncapsulation,
} from '@angular/core';
import {toSignal} from '@angular/core/rxjs-interop';
import {EMPTY_QUERY} from '@taiga-ui/cdk/constants';
import {tuiQueryListChanges} from '@taiga-ui/cdk/observables';
import {tuiInjectElement} from '@taiga-ui/cdk/utils/dom';
import {tuiIsNativeFocusedIn, tuiMoveFocus} from '@taiga-ui/cdk/utils/focus';
import {tuiIsPresent, tuiPure} from '@taiga-ui/cdk/utils/miscellaneous';
import {tuiIsPresent} from '@taiga-ui/cdk/utils/miscellaneous';
import {TUI_NOTHING_FOUND_MESSAGE} from '@taiga-ui/core/tokens';
import type {TuiSizeL, TuiSizeS} from '@taiga-ui/core/types';
import type {PolymorpheusContent} from '@taiga-ui/polymorpheus';
import {PolymorpheusOutlet} from '@taiga-ui/polymorpheus';
import type {Observable} from 'rxjs';
import {map} from 'rxjs';

import type {TuiDataListAccessor} from './data-list.tokens';
import {TUI_DATA_LIST_HOST, tuiAsDataListAccessor} from './data-list.tokens';
Expand All @@ -38,7 +36,7 @@ export function tuiInjectDataListSize(): TuiSizeL | TuiSizeS {
@Component({
standalone: true,
selector: 'tui-data-list',
imports: [NgIf, AsyncPipe, PolymorpheusOutlet],
imports: [NgIf, PolymorpheusOutlet],
templateUrl: './data-list.template.html',
styleUrls: ['./data-list.style.less'],
encapsulation: ViewEncapsulation.None,
Expand All @@ -48,14 +46,17 @@ export function tuiInjectDataListSize(): TuiSizeL | TuiSizeS {
role: 'listbox',
},
})
export class TuiDataListComponent<T> implements TuiDataListAccessor<T> {
export class TuiDataListComponent<T>
implements TuiDataListAccessor<T>, AfterContentChecked
{
@ContentChildren(forwardRef(() => TuiOption), {descendants: true})
private readonly options: QueryList<TuiOption<T>> = EMPTY_QUERY;

private origin?: HTMLElement;
private readonly el = tuiInjectElement();

protected readonly defaultEmptyContent$ = inject(TUI_NOTHING_FOUND_MESSAGE);
protected readonly fallback = toSignal(inject(TUI_NOTHING_FOUND_MESSAGE));
protected empty = true;

@Input()
public emptyContent: PolymorpheusContent;
Expand All @@ -82,18 +83,18 @@ export class TuiDataListComponent<T> implements TuiDataListAccessor<T> {
}
}

public ngAfterContentChecked(): void {
// TODO: Refactor to :has after Safari support bumped to 15
this.empty = !this.el.querySelector('[tuiOption]');
}

public getOptions(includeDisabled = false): readonly T[] {
return this.options
.filter(({disabled}) => includeDisabled || !disabled)
.map(({value}) => value)
.filter(tuiIsPresent);
}

@tuiPure
protected get empty$(): Observable<boolean> {
return tuiQueryListChanges(this.options).pipe(map(({length}) => !length));
}

@HostListener('focusin', ['$event.relatedTarget', '$event.currentTarget'])
protected onFocusIn(relatedTarget: HTMLElement, currentTarget: HTMLElement): void {
if (!currentTarget.contains(relatedTarget) && !this.origin) {
Expand Down
4 changes: 2 additions & 2 deletions projects/core/components/data-list/data-list.template.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<ng-content />
<div
*ngIf="empty$ | async"
*ngIf="empty"
class="t-empty"
>
<ng-container *polymorpheusOutlet="emptyContent || (defaultEmptyContent$ | async) as text">
<ng-container *polymorpheusOutlet="emptyContent || fallback() as text">
{{ text }}
</ng-container>
</div>
2 changes: 1 addition & 1 deletion projects/core/components/data-list/data-list.tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export interface TuiDataListAccessor<T = unknown> {
// TODO: Consider refactoring checkOption, it is only needed in ComboBox
export interface TuiDataListHost<T> {
checkOption?(option: T): void;
handleOption(option: T): void;
handleOption?(option: T): void;
readonly identityMatcher?: TuiIdentityMatcher<T>;
readonly stringify?: TuiStringHandler<T>;
readonly size?: TuiSizeL | TuiSizeS;
Expand Down
10 changes: 3 additions & 7 deletions projects/core/components/data-list/option.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import {TUI_DATA_LIST_HOST, TUI_OPTION_CONTENT} from './data-list.tokens';
type: 'button',
role: 'option',
'[attr.disabled]': 'disabled || null',
'[class._with-dropdown]': 'active',
'[class._with-dropdown]': 'dropdown?.()',
'(click)': 'onClick()',
'(mousemove.silent)': 'onMouseMove()',
},
Expand All @@ -62,7 +62,7 @@ export class TuiOption<T = unknown> implements OnDestroy {
protected readonly dropdown = inject(TuiDropdownDirective, {
self: true,
optional: true,
});
})?.ref;

@Input()
public disabled = false;
Expand All @@ -75,12 +75,8 @@ export class TuiOption<T = unknown> implements OnDestroy {
this.dataList?.handleFocusLossIfNecessary(this.el);
}

protected get active(): boolean {
return !!this.dropdown && !!this.dropdown.dropdownBoxRef;
}

protected onClick(): void {
if (this.host && this.value !== undefined) {
if (this.host?.handleOption && this.value !== undefined) {
this.host.handleOption(this.value);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export class TuiDropdownManual implements OnChanges {
private readonly driver = inject(TuiDropdownDriver);

@Input()
public tuiDropdownManual = false;
public tuiDropdownManual: boolean | '' = false;

public ngOnChanges(): void {
this.driver.next(this.tuiDropdownManual);
this.driver.next(!!this.tuiDropdownManual);
}
}
31 changes: 14 additions & 17 deletions projects/core/directives/dropdown/dropdown-open.directive.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {OnChanges} from '@angular/core';
import {
ChangeDetectorRef,
computed,
ContentChild,
Directive,
ElementRef,
Expand Down Expand Up @@ -40,7 +41,7 @@ function shouldClose(this: TuiDropdownOpen, event: Event | KeyboardEvent): boole
event.key.toLowerCase() === 'escape' &&
this.tuiDropdownEnabled &&
!!this.tuiDropdownOpen &&
!this.dropdown?.nextElementSibling
!this['dropdown']()?.nextElementSibling
);
}

Expand All @@ -65,14 +66,16 @@ export class TuiDropdownOpen implements OnChanges {
private readonly el = tuiInjectElement();
private readonly obscured = inject(TuiObscured);

private readonly dropdown = computed(
() => this.directive.ref()?.location.nativeElement,
);

protected readonly sub = merge(
this.obscured.tuiObscured.pipe(filter(Boolean)),
inject(TuiActiveZone).tuiActiveZoneChange.pipe(filter((a) => !a)),
fromEvent(this.el, 'focusin').pipe(
map(tuiGetActualTarget),
filter(
(target) => !this.host.contains(target) || !this.directive.dropdownBoxRef,
),
filter((target) => !this.host.contains(target) || !this.directive.ref()),
),
)
.pipe(tuiWatch(inject(ChangeDetectorRef)), takeUntilDestroyed())
Expand All @@ -90,10 +93,6 @@ export class TuiDropdownOpen implements OnChanges {
// TODO: make it private when all legacy controls will be deleted from @taiga-ui/legacy (5.0)
public readonly driver = inject(TuiDropdownDriver);

public get dropdown(): HTMLElement | undefined {
return this.directive.dropdownBoxRef?.location.nativeElement;
}

public ngOnChanges(): void {
this.drive();
}
Expand Down Expand Up @@ -166,7 +165,7 @@ export class TuiDropdownOpen implements OnChanges {
}

private get focused(): boolean {
return tuiIsNativeFocusedIn(this.host) || tuiIsNativeFocusedIn(this.dropdown);
return tuiIsNativeFocusedIn(this.host) || tuiIsNativeFocusedIn(this.dropdown());
}

private update(open: boolean): void {
Expand All @@ -185,20 +184,18 @@ export class TuiDropdownOpen implements OnChanges {
}

private focusDropdown(previous: boolean): void {
if (!this.dropdown) {
const root = this.dropdown();

if (!root) {
this.update(true);

return;
}

const doc = this.el.ownerDocument;
const child = this.dropdown.appendChild(doc.createElement('div'));
const initial = previous ? child : this.dropdown;
const focusable = tuiGetClosestFocusable({
initial,
previous,
root: this.dropdown,
});
const child = root.appendChild(doc.createElement('div'));
const initial = previous ? child : root;
const focusable = tuiGetClosestFocusable({initial, previous, root});

child.remove();
focusable?.focus();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ export class TuiDropdownPosition extends TuiPositionAccessor {
private previous?: TuiVerticalDirection;

public readonly type = 'dropdown';
public readonly accessor = tuiFallbackAccessor<TuiRectAccessor>('dropdown')(
inject<any>(TuiRectAccessor),
inject(TuiDropdownDirective),
);
public readonly accessor: TuiRectAccessor | null =
tuiFallbackAccessor<TuiRectAccessor>('dropdown')(
inject<any>(TuiRectAccessor),
inject(TuiDropdownDirective, {optional: true})!,
);

public getPosition({width, height}: DOMRect): TuiPoint {
if (!width && !height) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export class TuiDropdownSelection
* Check if Node is inside dropdown
*/
private boxContains(node: Node): boolean {
return !!this.dropdown.dropdownBoxRef?.location.nativeElement.contains(node);
return !!this.dropdown.ref()?.location.nativeElement.contains(node);
}

private veryVerySadInputFix(element: HTMLInputElement | HTMLTextAreaElement): Range {
Expand Down
19 changes: 10 additions & 9 deletions projects/core/directives/dropdown/dropdown.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
inject,
INJECTOR,
Input,
signal,
TemplateRef,
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
Expand Down Expand Up @@ -50,8 +51,8 @@ export class TuiDropdownDirective
protected readonly sub = this.refresh$
.pipe(throttleTime(0), takeUntilDestroyed())
.subscribe(() => {
this.dropdownBoxRef?.changeDetectorRef.detectChanges();
this.dropdownBoxRef?.changeDetectorRef.markForCheck();
this.ref()?.changeDetectorRef.detectChanges();
this.ref()?.changeDetectorRef.markForCheck();
});

public readonly el = tuiInjectElement();
Expand All @@ -61,7 +62,7 @@ export class TuiDropdownDirective
inject(INJECTOR),
);

public dropdownBoxRef: ComponentRef<unknown> | null = null;
public ref = signal<ComponentRef<unknown> | null>(null);
public content: PolymorpheusContent<TuiContext<() => void>>;

@Input()
Expand Down Expand Up @@ -96,13 +97,13 @@ export class TuiDropdownDirective
}

public toggle(show: boolean): void {
if (show && this.content && !this.dropdownBoxRef) {
this.dropdownBoxRef = this.service.add(this.component);
} else if (!show && this.dropdownBoxRef) {
const {dropdownBoxRef} = this;
const ref = this.ref();

this.dropdownBoxRef = null;
this.service.remove(dropdownBoxRef);
if (show && this.content && !ref) {
this.ref.set(this.service.add(this.component));
} else if (!show && ref) {
this.ref.set(null);
this.service.remove(ref);
}
}
}
6 changes: 3 additions & 3 deletions projects/core/directives/hint/hint.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export class TuiHintComponent<C = any> {
const rect = this.accessor.getClientRect();
const viewport = this.viewport.getClientRect();

if (rect === EMPTY_CLIENT_RECT) {
if (rect === EMPTY_CLIENT_RECT || !height || !width) {
return;
}

Expand All @@ -140,8 +140,8 @@ export class TuiHintComponent<C = any> {
this.apply(
tuiPx(Math.round(top)),
tuiPx(Math.round(safeLeft)),
tuiPx(Math.round(tuiClamp(beakTop, 0.5, height - 1))),
tuiPx(Math.round(tuiClamp(beakLeft, 0.5, width - 1))),
tuiPx(Math.round(tuiClamp(beakTop, 1, height - 1))),
tuiPx(Math.round(tuiClamp(beakLeft, 1, width - 1))),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@
<button
iconEnd="@tui.chevron-right"
tuiDropdownAlign="right"
tuiDropdownManual
tuiDropdownSided
tuiOption
[tuiDropdown]="burgersTmp"
[tuiDropdownManual]="false"
>
Burgers
</button>
<button
iconEnd="@tui.chevron-right"
tuiDropdownAlign="right"
tuiDropdownManual
tuiDropdownSided
tuiOption
[tuiDropdown]="drinksTmp"
[tuiDropdownManual]="false"
>
Drinks
</button>
Expand All @@ -60,10 +60,10 @@
iconEnd="@tui.chevron-right"
iconStart="@tui.menu"
tuiDropdownAlign="right"
tuiDropdownManual
tuiDropdownSided
tuiOption
[tuiDropdown]="drinksTmp"
[tuiDropdownManual]="false"
>
Nested menu
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,21 @@
automation-id="tui-data-list-calendar-option"
iconEnd="@tui.chevron-right"
tuiDropdownAlign="right"
tuiDropdownManual
tuiDropdownSided
tuiOption
[tuiDropdown]="calendar"
[tuiDropdownManual]="false"
>
📅 Calendar: {{ dateValue }}
</button>
<button
automation-id="tui-data-list-email-option"
iconEnd="@tui.chevron-right"
tuiDropdownAlign="right"
tuiDropdownManual
tuiDropdownSided
tuiOption
[tuiDropdown]="input"
[tuiDropdownManual]="false"
>
📧 Email: {{ emailValue }}
</button>
Expand All @@ -52,10 +52,10 @@
iconEnd="@tui.chevron-right"
tuiDropdownAlign="right"
tuiDropdownDirection="top"
tuiDropdownManual
tuiDropdownSided
tuiOption
[tuiDropdown]="range"
[tuiDropdownManual]="false"
>
⌛ Range: {{ rangeValue }}
</button>
Expand Down
Loading
Loading