Skip to content

fix(overflow-items,actions-panel): additional resize observer target (#DS-3885) #877

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

Merged
merged 3 commits into from
Jun 30, 2025
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
2 changes: 1 addition & 1 deletion packages/components/actions-panel/actions-panel.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#### Adaptability

The bulk actions panel becomes compact to fit in the available screen area.
The bulk actions panel becomes compact to fit within the available screen area. Adaptability is implemented using the [`KbqOverflowItems`](en/components/overflow-items) component, which automatically hides elements with dynamic adaptation to the container width. The `additionalResizeObserverTargets` attribute is used to track panel size changes, allowing you to monitor size changes of specified elements on the page.

<!-- example(actions-panel-adaptive) -->

Expand Down
2 changes: 1 addition & 1 deletion packages/components/actions-panel/actions-panel.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#### Адаптивность

Панель массовых действий становится компактной, чтобы помещаться в доступную область экрана.
Панель массовых действий становится компактной, чтобы помещаться в доступную область экрана. Адаптивность реализована при помощи компонента [`KbqOverflowItems`](ru/components/overflow-items), который автоматически скрывает элементы с динамической адаптацией под ширину контейнера. Для отслеживания изменений размеров панели используется атрибут `additionalResizeObserverTargets`, который позволяет отслеживать изменения размеров указанных элементов на странице.

<!-- example(actions-panel-adaptive) -->

Expand Down
13 changes: 13 additions & 0 deletions packages/components/overflow-items/overflow-items.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,16 @@ The order in which elements are hidden is determined using the `order` attribute

The delay for hiding/showing elements is set using the `debounceTime` attribute. Enabling this option positively
affects performance when there are many elements or when the container size changes frequently.

```html
<div kbqOverflowItems [debounceTime]="300">...</div>
```

### Additional target for ResizeObserver

Sometimes the container size can change not only when the browser window is resized, but also when other elements on the page change size (for example: [Sidebar](en/components/sidebar)). To track such changes, you can use the `additionalResizeObserverTargets` attribute.
The attribute accepts either a single element or an array of elements whose size changes will trigger recalculation of hidden elements. By default, `document.body` is tracked.

```html
<div kbqOverflowItems [additionalResizeObserverTargets]="sidebarContainerElement">...</div>
```
13 changes: 13 additions & 0 deletions packages/components/overflow-items/overflow-items.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,16 @@

Задержка скрытия/отображения элементов задается с помощью атрибута `debounceTime`. Включение этой опции позитивно
сказывается на производительности при большом количестве элементов, либо при частых изменениях размера контейнера.

```html
<div kbqOverflowItems [debounceTime]="300">...</div>
```

### Дополнительная цель для ResizeObserver

Иногда размер контейнера может изменяться не только при изменении размера окна браузера, но и при изменении размеров других элементов на странице (например: [Sidebar](ru/components/sidebar)). Для отслеживания таких изменений можно использовать атрибут `additionalResizeObserverTargets`.
Атрибут принимает либо один элемент, либо массив элементов, изменение размеров которых приведет к пересчету скрытых элементов. По умолчанию отслеживается `document.body`.

```html
<div kbqOverflowItems [additionalResizeObserverTargets]="sidebarContainerElement">...</div>
```
38 changes: 27 additions & 11 deletions packages/components/overflow-items/overflow-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
signal
} from '@angular/core';
import { outputToObservable, takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
import { debounceTime, merge, skip } from 'rxjs';
import { debounceTime, merge, skip, switchMap } from 'rxjs';

/**
* Manages the visibility of the element.
Expand Down Expand Up @@ -120,6 +120,11 @@ export class KbqOverflowItem extends ElementVisibilityManager {
host: { class: 'kbq-overflow-items' }
})
export class KbqOverflowItems {
private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
private readonly resizeObserver = inject(SharedResizeObserver);
private readonly renderer = inject(Renderer2);
private readonly document = inject(DOCUMENT);

/**
* `KbqOverflowItem` directive references.
*/
Expand All @@ -144,6 +149,13 @@ export class KbqOverflowItems {
*/
readonly debounceTime = input(0, { transform: numberAttribute });

/**
* List of additional elements to observe for resize changes.
*
* @default document.body
*/
readonly additionalResizeObserverTargets = input<Element | Element[]>(this.document.body);

/**
* Emits when the set of hidden items changes.
*/
Expand All @@ -156,20 +168,24 @@ export class KbqOverflowItems {
initialValue: new Set<unknown>([]) as ReadonlySet<unknown>
});

private readonly elementRef = inject<ElementRef<HTMLElement>>(ElementRef);
private readonly resizeObserver = inject(SharedResizeObserver);
private readonly renderer = inject(Renderer2);
private readonly document = inject(DOCUMENT);

constructor() {
this.setStyles();
this.setupObservers();
}

merge(
toObservable(this.items),
toObservable(this.reverseOverflowOrder).pipe(skip(1)),
private setupObservers(): void {
const resizeObservers = merge(
this.resizeObserver.observe(this.elementRef.nativeElement),
this.resizeObserver.observe(this.document.body)
)
toObservable(this.additionalResizeObserverTargets).pipe(
switchMap((targets) => {
return Array.isArray(targets)
? merge(...targets.map((target) => this.resizeObserver.observe(target)))
: this.resizeObserver.observe(targets);
})
)
);

merge(toObservable(this.items), toObservable(this.reverseOverflowOrder).pipe(skip(1)), resizeObservers)
.pipe(debounceTime(this.debounceTime()), takeUntilDestroyed())
.subscribe(() => {
const hiddenItems = this.getHiddenItems(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ type ExampleAction = {
<button (click)="open()" kbq-button>open</button>

<ng-template let-data>
<div #kbqOverflowItems="kbqOverflowItems" kbqOverflowItems>
<div
#kbqOverflowItems="kbqOverflowItems"
[additionalResizeObserverTargets]="additionalResizeObserverTargets"
kbqOverflowItems
>
<div [kbqOverflowItem]="action.Counter" order="99">
<div class="example-counter">Selected: {{ data.length }}</div>
<kbq-divider class="example-divider-vertical" [vertical]="true" />
Expand Down Expand Up @@ -84,7 +88,6 @@ type ExampleAction = {
align-items: center;
justify-content: center;
height: 64px;
overflow: hidden;
}

.kbq-overflow-item {
Expand Down Expand Up @@ -117,18 +120,19 @@ type ExampleAction = {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExampleActionsPanel {
private readonly actionsPanel = inject(KbqActionsPanel, { self: true });
private readonly elementRef = inject(ElementRef);
private readonly templateRef = viewChild.required(TemplateRef);
private actionsPanelRef: KbqActionsPanelRef | null;
private readonly toast = inject(KbqToastService);

protected readonly actions: ExampleAction[] = [
{ id: 'Responsible', icon: 'kbq-user_16' },
{ id: 'Status', icon: 'kbq-arrow-right-s_16' },
{ id: 'Archive', icon: 'kbq-box-archive-arrow-down_16' }
];
protected readonly action = { Counter: 'counter' };

private readonly actionsPanel = inject(KbqActionsPanel, { self: true });
private readonly elementRef = inject(ElementRef);
private readonly templateRef = viewChild.required(TemplateRef);
private actionsPanelRef: KbqActionsPanelRef | null;
private readonly toast = inject(KbqToastService);
protected readonly additionalResizeObserverTargets = this.elementRef.nativeElement;

constructor() {
afterNextRender(() => this.open());
Expand Down Expand Up @@ -173,15 +177,17 @@ export class ExampleActionsPanel {
<example-actions-panel [style.width.px]="89" />
`,
styles: `
:host {
min-width: 480px;
display: block;
}

div {
color: var(--kbq-foreground-contrast-secondary);
margin: var(--kbq-size-s) var(--kbq-size-s) 0;
}

example-actions-panel {
min-width: 89px;
max-width: 100%;
overflow: hidden;
resize: horizontal;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
Expand Down
3 changes: 2 additions & 1 deletion tools/public_api_guard/components/overflow-items.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,13 @@ export class KbqOverflowItem extends ElementVisibilityManager {
// @public
export class KbqOverflowItems {
constructor();
readonly additionalResizeObserverTargets: InputSignal<Element | Element[]>;
readonly changes: OutputEmitterRef<ReadonlySet<unknown>>;
readonly debounceTime: InputSignalWithTransform<number, unknown>;
readonly hiddenItemIDs: Signal<ReadonlySet<unknown>>;
readonly reverseOverflowOrder: InputSignalWithTransform<boolean, unknown>;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<KbqOverflowItems, "[kbqOverflowItems]", ["kbqOverflowItems"], { "reverseOverflowOrder": { "alias": "reverseOverflowOrder"; "required": false; "isSignal": true; }; "debounceTime": { "alias": "debounceTime"; "required": false; "isSignal": true; }; }, { "changes": "changes"; }, ["items", "result"], never, true, never>;
static ɵdir: i0.ɵɵDirectiveDeclaration<KbqOverflowItems, "[kbqOverflowItems]", ["kbqOverflowItems"], { "reverseOverflowOrder": { "alias": "reverseOverflowOrder"; "required": false; "isSignal": true; }; "debounceTime": { "alias": "debounceTime"; "required": false; "isSignal": true; }; "additionalResizeObserverTargets": { "alias": "additionalResizeObserverTargets"; "required": false; "isSignal": true; }; }, { "changes": "changes"; }, ["items", "result"], never, true, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<KbqOverflowItems, never>;
}
Expand Down