Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion packages/components/filter-bar/filter-bar-button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { KbqButton, KbqButtonStyles } from '@koobiq/components/button';
import { KbqComponentColors } from '@koobiq/components/core';
import { KbqFilterBar } from './filter-bar';
import { KbqFilters } from './filters';

@Directive({
selector: '[kbqFilterBarButton]'
selector: '[kbqFilterBarButton]',
host: {
'(click)': 'saveFocusedElement()',
'(keydown)': 'saveFocusedElement()'
}
})
export class KbqFilterBarButton {
private readonly button = inject(KbqButton);
/** KbqFilterBar instance */
private readonly filterBar = inject(KbqFilterBar);
/** KbqFilters instance */
protected readonly filters = inject(KbqFilters);

constructor() {
this.filterBar.changes.pipe(takeUntilDestroyed()).subscribe(() => {
Expand All @@ -23,4 +30,9 @@ export class KbqFilterBarButton {
}
});
}

/** @docs-private */
saveFocusedElement() {
this.filters.saveFocusedElement(this.button);
}
}
18 changes: 15 additions & 3 deletions packages/components/filter-bar/filters.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<button
#mainButton
kbq-button
kbq-title
kbqFilterBarButton
kbqPopover
[kbqDropdownTriggerFor]="savedFilters"
[kbqPopoverArrow]="false"
[kbqPopoverOffset]="popoverOffset"
[kbqPopoverClass]="'kbq-filters__save-as-new-filter-popover'"
[kbqPopoverContent]="saveAsNewPopoverContent"
[kbqPopoverFooter]="saveAsNewPopoverFooter"
Expand All @@ -13,8 +15,8 @@
[kbqPopoverPlacementPriority]="[placements.BottomLeft, placements.TopLeft]"
[kbqPopoverSize]="popoverSize"
[kbqTrigger]="'manual'"
[class.kbq-active]="opened"
[disabled]="isSaving"
[class.kbq-active]="focusedElementBeforeIs(mainButton) && opened"
(dropdownClosed)="searchControl.setValue('')"
(dropdownOpened)="onDropdownOpen()"
>
Expand All @@ -32,10 +34,12 @@

@if (filterBar.isChanged && !filterBar.isSaved) {
<button
#saveNewFilterButton
kbqTooltip="{{ localeData.saveNewFilterTooltip }}"
kbq-button
kbqFilterBarButton
class="kbq-button_action"
[class.kbq-active]="focusedElementBeforeIs(saveNewFilterButton) && opened"
[color]="colors.Empty"
[disabled]="isSaving"
(click)="openSaveAsNewFilterPopover()"
Expand All @@ -46,12 +50,14 @@

@if (filterBar.isSaved) {
<button
#filterActionsButton
kbq-button
kbqFilterBarButton
class="kbq-button_action"
[color]="colors.Empty"
[kbqDropdownTriggerFor]="filterActions"
[disabled]="isSaving"
[class.kbq-active]="focusedElementBeforeIs(filterActionsButton) && filterActionsOpened"
[ngClass]="{ 'kbq-button_changed-saved-filter': filterBar.isSavedAndChanged }"
>
<i kbq-icon="kbq-ellipsis-vertical_16"></i>
Expand Down Expand Up @@ -142,7 +148,7 @@
</button>
}
@if (!filterBar.isReadOnly) {
<button kbq-dropdown-item (click)="this.onRemoveFilter.next(this.filter!)">
<button kbq-dropdown-item (click)="removeFilter()">
<i kbq-icon="kbq-trash_16"></i>
{{ localeData.remove }}
</button>
Expand Down Expand Up @@ -191,7 +197,13 @@
>
{{ localeData.saveButton }}
</button>
<button kbq-button [color]="'contrast-fade'" [kbqStyle]="'filled'" [disabled]="isSaving" (click)="closePopover()">
<button
kbq-button
[color]="'contrast-fade'"
[kbqStyle]="'filled'"
[disabled]="isSaving"
(click)="closePopover(true)"
>
{{ localeData.cancelButton }}
</button>
</ng-template>
92 changes: 82 additions & 10 deletions packages/components/filter-bar/filters.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { FocusMonitor, FocusOrigin } from '@angular/cdk/a11y';
import { AsyncPipe, NgClass } from '@angular/common';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
ElementRef,
EventEmitter,
inject,
Expand All @@ -12,6 +14,7 @@ import {
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { FormControl, FormsModule, ReactiveFormsModule, UntypedFormControl, Validators } from '@angular/forms';
import { KbqAlertModule } from '@koobiq/components/alert';
import { KbqButton, KbqButtonModule, KbqButtonStyles } from '@koobiq/components/button';
Expand Down Expand Up @@ -60,6 +63,12 @@ import { KbqFilter, KbqSaveFilterError, KbqSaveFilterEvent, KbqSaveFilterStatuse
}
})
export class KbqFilters implements OnInit {
/** @docs-private */
protected readonly elementRef = inject(ElementRef);
/** @docs-private */
protected readonly destroyRef = inject(DestroyRef);
/** @docs-private */
protected readonly focusMonitor = inject(FocusMonitor);
/** @docs-private */
protected readonly placements = PopUpPlacements;
/** @docs-private */
Expand All @@ -73,9 +82,20 @@ export class KbqFilters implements OnInit {
/** @docs-private */
private readonly changeDetectorRef = inject(ChangeDetectorRef);

@ViewChild(KbqButton) private button: KbqButton;
@ViewChild(KbqPopoverTrigger) private popover: KbqPopoverTrigger;
@ViewChild(KbqDropdownTrigger) private dropdown: KbqDropdownTrigger;
/** @docs-private */
@ViewChild('mainButton') protected mainButton: KbqButton;
/** @docs-private */
@ViewChild('saveNewFilterButton') protected saveNewFilterButton: KbqButton;
/** @docs-private */
@ViewChild('filterActionsButton') protected filterActionsButton: KbqButton;

/** @docs-private */
@ViewChild(KbqPopoverTrigger) protected popover: KbqPopoverTrigger;
/** @docs-private */
@ViewChild(KbqDropdownTrigger) protected dropdown: KbqDropdownTrigger;
/** @docs-private */
@ViewChild('filterActionsButton') protected filterActionsDropdown: KbqDropdownTrigger;

@ViewChild('search') private search: ElementRef;
@ViewChild('newFilterName') private newFilterName: ElementRef;
@ViewChild('saveFilterButton') private saveFilterButton: KbqButton;
Expand All @@ -87,6 +107,8 @@ export class KbqFilters implements OnInit {

/** @docs-private */
popoverSize = PopUpSizes.Medium;
/** @docs-private */
popoverOffset: number = 4;

/** new filter name for saving */
filterName: FormControl<string | null>;
Expand Down Expand Up @@ -125,6 +147,11 @@ export class KbqFilters implements OnInit {
return this.popover?.isOpen || this.dropdown?.opened;
}

/** Component state. true if opened dropdown or popup of filterActions */
get filterActionsOpened(): boolean {
return this.popover?.isOpen || this.filterActionsDropdown?.opened;
}

/** Selected filter */
get filter(): KbqFilter | null {
return this.filterBar.filter;
Expand All @@ -141,6 +168,16 @@ export class KbqFilters implements OnInit {
return this.filterBar.configuration.filters;
}

/** Current focus origin state.
* @docs-private */
get focusOrigin(): FocusOrigin {
return this._focusOrigin;
}

private _focusOrigin: FocusOrigin = null;

private focusedElementBeforeOpen: KbqButton | null;

constructor() {
this.filterBar.changes.subscribe(() => this.changeDetectorRef.markForCheck());
}
Expand All @@ -150,6 +187,19 @@ export class KbqFilters implements OnInit {
of(this.filters),
this.searchControl.valueChanges.pipe(map((value) => this.getFilteredOptions(value)))
);

this.focusMonitor
.monitor(this.elementRef, true)
.pipe(
filter((origin) => !!origin),
takeUntilDestroyed(this.destroyRef)
)
.subscribe((origin) => (this._focusOrigin = origin));
}

/** @docs-private */
focusedElementBeforeIs(button: KbqButton): boolean {
return this.focusedElementBeforeOpen === button;
}

selectFilter(filter: KbqFilter) {
Expand Down Expand Up @@ -210,7 +260,10 @@ export class KbqFilters implements OnInit {
}

restoreFocus() {
this.button.focus();
if (this.focusedElementBeforeOpen && !this.focusedElementBeforeOpen.disabled) {
this.focusMonitor.focusVia(this.focusedElementBeforeOpen.elementRef, this.focusOrigin);
this.focusedElementBeforeOpen = null;
}
}

preparePopover() {
Expand All @@ -221,11 +274,19 @@ export class KbqFilters implements OnInit {
this.popover.show();

merge(...this.popover.defaultClosingActions())
.pipe(filter(() => !this.isSaving))
.pipe(
filter(() => !this.isSaving),
takeUntilDestroyed(this.popover.instanceDestroyRef)
)
.subscribe(() => this.closePopover(false));

this.popover.visibleChange
.pipe(
filter((state) => !state),
takeUntilDestroyed(this.popover.instanceDestroyRef)
)
.subscribe(this.closePopover);

this.popover.visibleChange.pipe(filter((state) => !state)).subscribe(this.closePopover);

setTimeout(() => {
this.newFilterName.nativeElement.focus();
this.filterName.setErrors(null);
Expand All @@ -244,10 +305,15 @@ export class KbqFilters implements OnInit {
this.preparePopover();
}

closePopover = () => {
/** @docs-private */
saveFocusedElement(button?: KbqButton) {
this.focusedElementBeforeOpen = button || null;
}

closePopover = (restoreFocus: boolean = true) => {
this.popover.hide();

this.restoreFocus();
if (restoreFocus) this.restoreFocus();

setTimeout(() => this.changeDetectorRef.detectChanges());

Expand Down Expand Up @@ -280,14 +346,20 @@ export class KbqFilters implements OnInit {
this.onResetFilterChanges.emit(this.filter!);
}

removeFilter() {
this.onRemoveFilter.next(this.filter!);

setTimeout(() => this.focusMonitor.focusVia(this.mainButton.elementRef, this.focusOrigin), 0);
}

/** Hide the popup and restore focus.
* Use this method in the onSave, onSaveAsNew, or onChangeFilter events after the data has been successfully saved. */
filterSavedSuccessfully() {
this.isSaving = false;
this.popover.preventClose = false;

this.popover.hide();
this.restoreFocus();
setTimeout(() => this.restoreFocus(), 0);

this.changeDetectorRef.markForCheck();
}
Expand Down
6 changes: 6 additions & 0 deletions packages/components/popover/popover.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
DestroyRef,
Directive,
ElementRef,
EventEmitter,
Expand Down Expand Up @@ -374,6 +375,11 @@ export class KbqPopoverTrigger extends KbqPopUpTrigger<KbqPopoverComponent> impl
return this.trigger.includes(PopUpTriggers.Click);
}

/** @docs-private */
get instanceDestroyRef(): DestroyRef {
return this.instance.destroyRef;
}

@Input() backdropClass: string = 'cdk-overlay-transparent-backdrop';

// @TODO add realization for arrow (#DS-2514)
Expand Down
4 changes: 2 additions & 2 deletions packages/components/title/title.directive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class KbqTitleDirective extends KbqTooltipTrigger implements AfterViewIni
/** For special cases where the difference is a fraction of a pixel */
if (
!this.isVerticalOverflown &&
(this.child.scrollWidth === 0 || this.parent.offsetWidth === this.child.scrollWidth)
(this.child.scrollWidth === 0 || this.parent?.offsetWidth === this.child.scrollWidth)
) {
if (this.hasOnlyText) {
const wrapper = this.renderer.createElement('span');
Expand All @@ -66,7 +66,7 @@ export class KbqTitleDirective extends KbqTooltipTrigger implements AfterViewIni
}

get isHorizontalOverflown(): boolean {
return this.parent.offsetWidth < this.child.scrollWidth;
return this.parent?.offsetWidth < this.child.scrollWidth;
}

get isVerticalOverflown(): boolean {
Expand Down
Loading