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

fix(components/ag-grid): option to show horizontal scrollbar at top when using trackpad #552

Merged
merged 4 commits into from
Sep 27, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe(`ag-grid-storybook data manager`, () => {
});

it(`should render ag-grid with data manager, ${label} layout`, () => {
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.get('#ready')
.should('exist')
.end()
Expand All @@ -30,13 +31,16 @@ describe(`ag-grid-storybook data manager`, () => {
.should('have.length.gt', 14)
.should('have.descendants', '.sky-switch-control')
.end()
// Necessary to wait for the grid to render.
.wait(1000)
.get('#root')
.screenshot(
`datamanagercomponent-datamanager--data-manager-${domLayout}-${theme}`,
{
clip: { x: 0, y: 0, width: 1300, height: 600 },
}
)
.get('#root')
.percySnapshot(
`datamanagercomponent-datamanager--data-manager-${domLayout}-${theme}`,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,8 +358,16 @@ it('should move the horizontal scroll based on enableTopScroll check', async ()
fixture.componentInstance.agGrid.gridReady.emit();
fixture.detectChanges();
await fixture.whenStable();
const scrollElement = fixture.nativeElement.querySelectorAll(
'.ag-body-horizontal-scroll'
);
expect(scrollElement.length).toEqual(1);
const gridComponents: string[] = Array.from(
fixture.nativeElement.querySelector('.ag-root')?.children || []
).map((el: HTMLElement) => el.classList[0]);
// Expect the scrollbar below the header.
expect(gridComponents).toEqual([
'ag-header',
'ag-body-horizontal-scroll',
'ag-floating-top',
'ag-body-viewport',
'ag-floating-bottom',
'ag-overlay',
]);
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,24 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SkyAppTestUtility, expect } from '@skyux-sdk/testing';

import { AgGridAngular } from 'ag-grid-angular';
import { Column, ColumnApi, DetailGridInfo, GridApi } from 'ag-grid-community';
import {
Column,
ColumnApi,
DetailGridInfo,
FirstDataRenderedEvent,
GridApi,
GridReadyEvent,
RowDataChangedEvent,
} from 'ag-grid-community';
import { Subject } from 'rxjs';

import { SkyAgGridAdapterService } from './ag-grid-adapter.service';
import { SkyAgGridWrapperComponent } from './ag-grid-wrapper.component';
import { SkyAgGridModule } from './ag-grid.module';
import {
EnableTopScroll,
SkyAgGridFixtureComponent,
} from './fixtures/ag-grid.component.fixture';

describe('SkyAgGridWrapperComponent', () => {
let gridAdapterService: SkyAgGridAdapterService;
Expand All @@ -17,6 +30,9 @@ describe('SkyAgGridWrapperComponent', () => {
const agGrid: AgGridAngular = {
api: new GridApi(),
columnApi: new ColumnApi(),
gridReady: new Subject<GridReadyEvent>(),
rowDataChanged: new Subject<RowDataChangedEvent>(),
firstDataRendered: new Subject<FirstDataRenderedEvent>(),
} as AgGridAngular;

beforeEach(() => {
Expand Down Expand Up @@ -255,3 +271,90 @@ describe('SkyAgGridWrapperComponent', () => {
});
});
});

describe('SkyAgGridWrapperComponent via fixture', () => {
let gridWrapperFixture: ComponentFixture<SkyAgGridFixtureComponent>;
let gridWrapperNativeElement: HTMLElement;
const getChildrenClassNames = () =>
Array.from(
gridWrapperNativeElement.querySelector('.ag-root')?.children || []
).map((el: HTMLElement) => el.classList[0]);

it('should move the horizontal scroll based on enableTopScroll check, static data', async () => {
TestBed.configureTestingModule({
imports: [SkyAgGridModule],
providers: [
{
provide: EnableTopScroll,
useValue: true,
},
],
});
gridWrapperFixture = TestBed.createComponent(SkyAgGridFixtureComponent);
gridWrapperNativeElement = gridWrapperFixture.nativeElement;

gridWrapperFixture.detectChanges();
await gridWrapperFixture.whenStable();

// Expect the scrollbar at the bottom.
expect(getChildrenClassNames()).toEqual([
'ag-header',
'ag-body-horizontal-scroll',
'ag-floating-top',
'ag-body-viewport',
'ag-floating-bottom',
'ag-overlay',
]);
});

it('should move the horizontal scroll based on enableTopScroll check, async loading', async () => {
TestBed.configureTestingModule({
imports: [SkyAgGridModule],
});
gridWrapperFixture = TestBed.createComponent(SkyAgGridFixtureComponent);
gridWrapperNativeElement = gridWrapperFixture.nativeElement;

gridWrapperFixture.detectChanges();
await gridWrapperFixture.whenStable();

// Expect the scrollbar at the bottom.
expect(getChildrenClassNames()).toEqual([
'ag-header',
'ag-floating-top',
'ag-body-viewport',
'ag-floating-bottom',
'ag-body-horizontal-scroll',
'ag-overlay',
]);

gridWrapperFixture.componentInstance.gridOptions.context = {
enableTopScroll: true,
};
gridWrapperFixture.componentInstance.agGrid.gridReady.emit();

// Expect the scrollbar below the header.
expect(getChildrenClassNames()).toEqual([
'ag-header',
'ag-body-horizontal-scroll',
'ag-floating-top',
'ag-body-viewport',
'ag-floating-bottom',
'ag-overlay',
]);

gridWrapperFixture.componentInstance.gridOptions.context = {
enableTopScroll: false,
};
gridWrapperFixture.componentInstance.agGrid.rowDataChanged.emit();

// Expect the scrollbar at the bottom.
expect(getChildrenClassNames()).toEqual([
'ag-header',
'ag-floating-top',
'ag-body-viewport',
'ag-floating-bottom',
'ag-body-horizontal-scroll',
'ag-overlay',
]);
});
});
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { DOCUMENT } from '@angular/common';
import {
AfterContentInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChild,
ElementRef,
Inject,
OnDestroy,
} from '@angular/core';

import { AgGridAngular } from 'ag-grid-angular';
import { DetailGridInfo } from 'ag-grid-community';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

import { SkyAgGridAdapterService } from './ag-grid-adapter.service';

Expand All @@ -19,7 +24,7 @@ let idIndex = 0;
templateUrl: './ag-grid-wrapper.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SkyAgGridWrapperComponent implements AfterContentInit {
export class SkyAgGridWrapperComponent implements AfterContentInit, OnDestroy {
@ContentChild(AgGridAngular, {
static: true,
})
Expand All @@ -40,10 +45,13 @@ export class SkyAgGridWrapperComponent implements AfterContentInit {

private _viewkeeperClasses: string[] = [];

#ngUnsubscribe = new Subject<void>();

constructor(
private adapterService: SkyAgGridAdapterService,
private changeDetector: ChangeDetectorRef,
private elementRef: ElementRef
private elementRef: ElementRef,
@Inject(DOCUMENT) private document: Document
) {
idIndex++;
this.afterAnchorId = 'sky-ag-grid-nav-anchor-after-' + idIndex;
Expand All @@ -62,6 +70,24 @@ export class SkyAgGridWrapperComponent implements AfterContentInit {
this.viewkeeperClasses.push('.ag-header');
}
}
this.agGrid.gridReady.pipe(takeUntil(this.#ngUnsubscribe)).subscribe(() => {
this.#moveHorizontalScroll();
});
this.agGrid.firstDataRendered
.pipe(takeUntil(this.#ngUnsubscribe))
.subscribe(() => {
this.#moveHorizontalScroll();
});
this.agGrid.rowDataChanged
.pipe(takeUntil(this.#ngUnsubscribe))
.subscribe(() => {
this.#moveHorizontalScroll();
});
}

public ngOnDestroy(): void {
this.#ngUnsubscribe.next();
this.#ngUnsubscribe.complete();
}

/**
Expand Down Expand Up @@ -127,4 +153,42 @@ export class SkyAgGridWrapperComponent implements AfterContentInit {
return false;
}
}

#moveHorizontalScroll() {
if (this.agGrid && this.agGrid.api) {
const toTop = !!this.agGrid.gridOptions.context?.enableTopScroll;
const root: HTMLElement =
this.elementRef.nativeElement.querySelector('.ag-root');
const header: HTMLDivElement | null = root.querySelector('.ag-header');
const floatingBottom: HTMLDivElement | null = root.querySelector(
'.ag-floating-bottom'
);
const scrollbar: HTMLDivElement | null = root.querySelector(
'.ag-body-horizontal-scroll'
);
if (header && floatingBottom && scrollbar) {
if (
scrollbar.style.height !==
scrollbar.style.getPropertyValue(
'--sky-ag-body-horizontal-scroll-width'
)
) {
scrollbar.style.setProperty(
'--sky-ag-body-horizontal-scroll-width',
scrollbar.style.height
);
}
const isTop = !!root.children[1].matches('.ag-body-horizontal-scroll');
if (toTop && !isTop) {
const fragment = this.document.createDocumentFragment();
fragment.appendChild(scrollbar);
header.after(fragment);
} else if (!toTop && isTop) {
const fragment = this.document.createDocumentFragment();
fragment.appendChild(scrollbar);
floatingBottom.after(fragment);
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<sky-ag-grid-wrapper>
<ag-grid-angular
#agGrid
class="sky-ag-grid-editable"
[gridOptions]="gridOptions"
[rowData]="gridData"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import {
Component,
Inject,
InjectionToken,
OnInit,
Optional,
ViewChild,
ViewEncapsulation,
} from '@angular/core';

import { AgGridAngular } from 'ag-grid-angular';
import { GridOptions } from 'ag-grid-community';

import { SkyAgGridService } from '../ag-grid.service';
import { SkyCellType } from '../types/cell-type';

import { SKY_AG_GRID_DATA, SKY_AG_GRID_LOOKUP } from './ag-grid-data.fixture';

export const EnableTopScroll = new InjectionToken('EnableTopScroll');

@Component({
selector: 'sky-ag-grid-component-fixture',
templateUrl: './ag-grid.component.fixture.html',
encapsulation: ViewEncapsulation.None,
})
export class SkyAgGridFixtureComponent implements OnInit {
@ViewChild('agGrid', { static: true })
public agGrid: AgGridAngular;

public gridData = SKY_AG_GRID_DATA;
public columnDefs = [
{
Expand Down Expand Up @@ -130,9 +144,17 @@ export class SkyAgGridFixtureComponent implements OnInit {
public gridOptions: GridOptions = {
columnDefs: this.columnDefs,
suppressColumnVirtualisation: true,
context: {
enableTopScroll: this.enableTopScroll,
},
};

constructor(private gridService: SkyAgGridService) {}
constructor(
private gridService: SkyAgGridService,
@Optional()
@Inject(EnableTopScroll)
public enableTopScroll: boolean | undefined
) {}

public ngOnInit(): void {
this.gridOptions = this.gridService.getEditableGridOptions({
Expand Down
33 changes: 14 additions & 19 deletions libs/components/ag-grid/src/lib/styles/ag-grid-styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,23 @@ $sky-cell-wrap-text-line-height-modern: $sky-theme-modern-font-paragraph-line-he
}

.sky-ag-grid.sky-ag-grid-top-scrollbar .ag-root {
> .ag-header {
order: 1;
}

> .ag-body-horizontal-scroll {
order: 2;
}

> .ag-floating-top {
order: 3;
}

> .ag-body-viewport {
order: 4;
}
.ag-center-cols-viewport {
/* Prevent a double scrollbar. */
scrollbar-width: none;

> .ag-floating-bottom {
order: 5;
&::-webkit-scrollbar {
display: none;
}
}
}

> .ag-overlay {
order: 6;
.ag-body-horizontal-scroll.ag-scrollbar-invisible {
/* Value is overridden in SkyAgGridWrapperComponent to match the calculated value AG Grid uses. */
--sky-ag-body-horizontal-scroll-width: 15px;
position: relative;
z-index: 1;
bottom: revert;
margin-bottom: calc(var(--sky-ag-body-horizontal-scroll-width) * -1);
}
}

Expand Down