Skip to content
Open
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
8 changes: 8 additions & 0 deletions packages/components/actions-panel/actions-panel-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export class KbqActionsPanelRef<I = unknown, R = unknown> {
return this.dialogRef.closed;
}

/** Gets an observable that is notified when the actions panel starts closing, before its exit animation plays. */
get beforeClosed(): Observable<R | undefined> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а зчем здесь геттер ?

@artembelik artembelik Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

сделано по аналогии с afterClosed (выше реализовано), как предлагаешь сделать?

return this._beforeClosed;
}

/** Gets an observable that emits when keydown events are targeted on the overlay. */
get keydownEvents(): Observable<KeyboardEvent> {
return this.dialogRef.keydownEvents;
Expand Down Expand Up @@ -48,6 +53,7 @@ export class KbqActionsPanelRef<I = unknown, R = unknown> {
}

private readonly _afterOpened = new Subject<void>();
private readonly _beforeClosed = new Subject<R | undefined>();

/** Result to be passed down to the `afterClosed` stream. */
private result: R | undefined;
Expand Down Expand Up @@ -83,6 +89,8 @@ export class KbqActionsPanelRef<I = unknown, R = unknown> {
});

this.result = result;
this._beforeClosed.next(result);
this._beforeClosed.complete();
this.containerInstance.startCloseAnimation();
this.containerInstance = null!;
}
Expand Down
46 changes: 46 additions & 0 deletions packages/components/actions-panel/actions-panel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,52 @@ describe(KbqActionsPanelModule.name, () => {
expect(spy).toHaveBeenCalledTimes(1);
});

it('should emit beforeClosed synchronously on close, before afterClosed', async () => {
const fixture = createComponent(ActionsPanelController);
const { componentInstance } = fixture;
const actionsPanelRef = componentInstance.openFromTemplate();
const emitted: string[] = [];

actionsPanelRef.beforeClosed.subscribe(() => emitted.push('beforeClosed'));
actionsPanelRef.afterClosed.subscribe(() => emitted.push('afterClosed'));

componentInstance.close();
expect(emitted).toEqual(['beforeClosed']);

await fixture.whenStable();
expect(emitted).toEqual(['beforeClosed', 'afterClosed']);
});

it('should emit beforeClosed with result', async () => {
const { componentInstance } = createComponent(ActionsPanelController);
const actionsPanelRef = componentInstance.openFromTemplate();
const beforeClosed = lastValueFrom(actionsPanelRef.beforeClosed);

componentInstance.close('customResult');
expect(await beforeClosed).toBe('customResult');
});

it('should complete beforeClosed after it emits', () => {
const { componentInstance } = createComponent(ActionsPanelController);
const actionsPanelRef = componentInstance.openFromTemplate();
const completeSpy = jest.fn();

actionsPanelRef.beforeClosed.subscribe({ complete: completeSpy });
componentInstance.close();
expect(completeSpy).toHaveBeenCalledTimes(1);
});

it('should not emit beforeClosed again when close is called a second time', () => {
const { componentInstance } = createComponent(ActionsPanelController);
const actionsPanelRef = componentInstance.openFromTemplate();
const spy = jest.fn();

actionsPanelRef.beforeClosed.subscribe(spy);
componentInstance.close();
actionsPanelRef.close();
expect(spy).toHaveBeenCalledTimes(1);
});

it('should emit afterOpened on open', async () => {
const fixture = createComponent(ActionsPanelController);
const { componentInstance } = fixture;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
DestroyRef,
ElementRef,
inject,
input,
output,
Signal,
signal,
Expand Down Expand Up @@ -73,15 +74,34 @@ type ExampleTableItem = unknown;
width: 100%;
}

/** Adding extra space for actions panel */
/*
* Reserve space for the actions panel only while it's open, animating in sync with its
* own slide transition instead of a permanent gap that's still there once the panel is
* closed. Duration/curve values below are copied from KBQ_ACTIONS_PANEL_CONTAINER_ANIMATION
* (packages/components/actions-panel/actions-panel-container.ts) — not importable here since
* that trigger is defined for the panel's own 'state' animation, not exposed as CSS. Keep in
* sync if that trigger's durations/curve ever change.
*/
:host ::ng-deep .ag-body-viewport,
:host ::ng-deep .ag-body-vertical-scroll {
padding-bottom: 80px;
padding-bottom: 0;
transition: padding-bottom 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

:host(.example-grid_actions-panel-opened) ::ng-deep .ag-body-viewport,
:host(.example-grid_actions-panel-opened) ::ng-deep .ag-body-vertical-scroll {
/* Actions panel height with margins */
padding-bottom: 72px;
transition: padding-bottom 125ms cubic-bezier(0.4, 0, 0.2, 1);
}
Comment thread
Copilot marked this conversation as resolved.
`,
changeDetection: ChangeDetectionStrategy.OnPush
changeDetection: ChangeDetectionStrategy.OnPush,
host: {
'[class.example-grid_actions-panel-opened]': 'panelOpened()'
}
})
export class ExampleGrid {
readonly panelOpened = input(false);
private api: GridApi<ExampleTableItem> | null = null;
protected readonly defaultColDef: ColDef = {
sortable: true,
Expand All @@ -98,27 +118,11 @@ export class ExampleGrid {
hideDisabledCheckboxes: false
};
protected readonly columnDefs: ColDef[] = [
{
field: 'column0',
headerName: 'Project name',
pinned: true
},
{
field: 'column1',
headerName: 'Text'
},
{
field: 'column2',
headerName: 'Text'
},
{
field: 'column3',
headerName: 'Text'
},
{
field: 'column4',
headerName: 'Text'
}
{ field: 'column0', headerName: 'Project name', pinned: true },
{ field: 'column1', headerName: 'Text' },
{ field: 'column2', headerName: 'Text' },
{ field: 'column3', headerName: 'Text' },
{ field: 'column4', headerName: 'Text' }
];
protected readonly rowData = Array.from({ length: 33 }, (_, index) => ({
column0: 'Project name ' + index,
Expand Down Expand Up @@ -268,7 +272,7 @@ export class ExampleActionsPanel {
selector: 'actions-panel-overview-example',
imports: [ExampleGrid],
template: `
<example-grid (selectedItems)="toggleActionsPanel($event)" />
<example-grid [panelOpened]="panelOpened()" (selectedItems)="toggleActionsPanel($event)" />
`,
styles: `
:host {
Expand All @@ -283,9 +287,10 @@ export class ActionsPanelOverviewExample {
private readonly actionsPanel = inject(KbqActionsPanel, { self: true });
private readonly container = viewChild.required(ExampleGrid, { read: ElementRef });
private readonly grid = viewChild.required(ExampleGrid);
private actionsPanelRef: KbqActionsPanelRef<ExampleActionsPanel> | null;
private actionsPanelRef!: KbqActionsPanelRef<ExampleActionsPanel> | null;
private readonly data = signal<ExampleTableItem[]>([]);
private readonly destroyRef = inject(DestroyRef);
Comment thread
artembelik marked this conversation as resolved.
protected readonly panelOpened = signal(false);

protected toggleActionsPanel(selectedItems: ExampleTableItem[]): void {
if (selectedItems.length === 0) return this.actionsPanel.close();
Expand All @@ -298,11 +303,18 @@ export class ActionsPanelOverviewExample {
data: this.data,
overlayContainer: this.container()
});
this.panelOpened.set(true);

this.actionsPanelRef.afterOpened.subscribe(() => {
console.log('ActionsPanel opened');
});

// Fires as soon as close() is called, before the exit animation plays — so the grid's
// padding starts collapsing in sync with the panel's own slide-down instead of ~150ms late.
this.actionsPanelRef.beforeClosed.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
this.panelOpened.set(false);
});

this.actionsPanelRef.afterClosed.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((result) => {
console.log('ActionsPanel closed by action:', result);

Expand Down
1 change: 1 addition & 0 deletions tools/public_api_guard/components/actions-panel.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export class KbqActionsPanelRef<I = unknown, R = unknown> {
constructor(dialogRef: DialogRef<R, I>, containerInstance: KbqActionsPanelContainer);
get afterClosed(): Observable<R | undefined>;
get afterOpened(): Observable<void>;
get beforeClosed(): Observable<R | undefined>;
close(result?: R): void;
containerInstance: KbqActionsPanelContainer;
get id(): string;
Expand Down