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
36 changes: 35 additions & 1 deletion packages/components/grid/src/grid.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import '@sl-design-system/button/register.js';
import { ArrayListDataSource } from '@sl-design-system/data-source';
import '@sl-design-system/menu/register.js';
import { isPopoverOpen } from '@sl-design-system/shared';
import { type ToolBar } from '@sl-design-system/tool-bar';
Expand All @@ -13,7 +14,7 @@ import '../register.js';
import { type Grid, type SlActiveRowChangeEvent } from './grid.js';
import { waitForGridToRenderData } from './utils.js';

type Person = { firstName: string; lastName: string; email?: string };
type Person = { firstName: string; lastName: string; email?: string; group?: string };

describe('sl-grid', () => {
let el: Grid<Person>;
Expand Down Expand Up @@ -737,4 +738,37 @@ describe('sl-grid', () => {
expect(toolBar!.items.filter(item => item.visible).length).to.equal(5);
});
});

describe('group headers with sticky columns', () => {
it('should make the group header follow the sticky start columns', async () => {
const dataSource = new ArrayListDataSource(
[
{ firstName: 'John', lastName: 'Doe', group: 'A' },
{ firstName: 'Jane', lastName: 'Smith', group: 'A' }
],
{ groupBy: 'group' }
);

el = await fixture(html`
<sl-grid .dataSource=${dataSource}>
<sl-grid-column path="firstName" sticky width="100"></sl-grid-column>
<sl-grid-column path="lastName" sticky width="100"></sl-grid-column>
<sl-grid-column path="email"></sl-grid-column>
</sl-grid>
`);

await waitForGridToRenderData(el);
await el.updateComplete;

const groupHeader = el.renderRoot.querySelector<HTMLElement>(
'tbody tr[part~="group"] sl-grid-group-header'
);

expect(groupHeader).to.exist;
expect(groupHeader?.classList.contains('sticky-start-last')).to.be.true;
expect(groupHeader?.style.position).to.equal('sticky');
expect(groupHeader?.style.insetInlineStart).to.equal('0px');
expect(groupHeader?.style.inlineSize).to.equal('200px');
});
});
});
35 changes: 34 additions & 1 deletion packages/components/grid/src/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
render
} from 'lit';
import { property, query, state } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { GridColumnGroup } from './column-group.js';
import { GridColumn } from './column.js';
import { GridDragHandleColumn } from './drag-handle-column.js';
Expand Down Expand Up @@ -557,7 +558,7 @@ export class Grid<T = any> extends ScopedElementsMixin(LitElement) {
})}
${rows[rows.length - 1].map((col, index) => {
return `
:where(tbody td, thead tr th):nth-child(${index + 1}) {
:where(tbody tr:not([part~='group']) td, thead tr th):nth-child(${index + 1}) {
flex-grow: ${col.grow};
inline-size: ${col.width || '100'}px;
justify-content: ${col.align ?? 'start'};
Expand Down Expand Up @@ -630,6 +631,8 @@ export class Grid<T = any> extends ScopedElementsMixin(LitElement) {
draggable = !!this.#columnDefinitions.find(
col => !col.hidden && col instanceof GridDragHandleColumn
),
groupHeaderClasses = this.#getGroupHeaderClasses(),
groupHeaderStyles = this.#getGroupHeaderStyles(),
selectable = !!this.#columnDefinitions.find(
col => !col.hidden && col instanceof GridSelectionColumn
);
Expand All @@ -640,10 +643,12 @@ export class Grid<T = any> extends ScopedElementsMixin(LitElement) {
<sl-grid-group-header
@sl-select=${(event: SlSelectEvent<boolean>) => this.#onGroupSelect(event, item)}
@sl-toggle=${(event: SlToggleEvent<boolean>) => this.#onGroupToggle(event, item)}
class=${ifDefined(groupHeaderClasses.join(' ') || undefined)}
?collapsed=${collapsed}
?drag-handle=${draggable}
?selectable=${selectable}
.selected=${item.selected ?? 'none'}
style=${ifDefined(groupHeaderStyles)}
>
${this.groupHeaderRenderer?.(item) ??
html`
Expand Down Expand Up @@ -1208,6 +1213,28 @@ export class Grid<T = any> extends ScopedElementsMixin(LitElement) {
}
}

#getGroupHeaderClasses(): string[] {
const columns = this.#getStickyStartColumns();

if (!columns.length) {
return [];
}

return [`sticky-start-${columns.length > 1 ? 'last' : 'first'}`];
}

#getGroupHeaderStyles(): string | undefined {
const columns = this.#getStickyStartColumns();

if (!columns.length) {
return undefined;
}

const inlineSize = columns.reduce((acc, { width }) => acc + (width ?? 100), 0);

return `flex-grow: 0; inline-size: ${inlineSize}px; inset-inline-start: 0px; position: sticky;`;
}

/** Returns the left offset, taking any sticky columns into account. */
#getStickyColumnOffset(index: number): number {
let columns: Array<GridColumn<T>>;
Expand All @@ -1221,6 +1248,12 @@ export class Grid<T = any> extends ScopedElementsMixin(LitElement) {
return columns.filter(col => !col.hidden).reduce((acc, { width = 0 }) => acc + width, 0);
}

#getStickyStartColumns(): Array<GridColumn<T>> {
return this.#columnDefinitions.filter(
col => !col.hidden && col.sticky && col.stickyPosition === 'start'
);
}

#removeColumn(col: GridColumn): void {
if (col instanceof GridSortColumn) {
if (col.direction) {
Expand Down
49 changes: 49 additions & 0 deletions packages/components/grid/src/stories/grouping.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,52 @@ export const CustomGroupHeader: Story = {
`;
}
};

export const StickyColumnsWithCustomGroupHeader: Story = {
loaders: [async () => ({ students: (await getStudents()).students })],
render: (_, { loaded: { students } }) => {
const dataSource = new ArrayListDataSource(students as Student[], {
groupBy: 'school.id',
groupLabelPath: 'school.name'
});

const groupHeaderRenderer = (item: ListDataSourceGroupItem) => {
return html`
<span slot="group-heading">${item.label} (${item.count})</span>
<sl-button size="sm">Add student</sl-button>
`;
};

return html`
<style>
html {
display: block;
}
</style>
<p>
This example shows grouped rows with a custom group header and sticky columns while
scrolling horizontally.
</p>
<sl-grid
.dataSource=${dataSource}
.groupHeaderRenderer=${groupHeaderRenderer}
.scopedElements=${{ 'sl-button': Button }}
>
<sl-grid-column grow="0" header="Nr." path="studentNumber" sticky></sl-grid-column>
<sl-grid-column path="group.name" sticky></sl-grid-column>
<sl-grid-column
grow="3"
header="Student"
path="fullName"
.renderer=${avatarRenderer}
.scopedElements=${{ 'sl-avatar': Avatar }}
></sl-grid-column>
<sl-grid-column path="email"></sl-grid-column>
<sl-grid-column path="school.name"></sl-grid-column>
<sl-grid-column path="school.address"></sl-grid-column>
<sl-grid-column path="school.city"></sl-grid-column>
<sl-grid-column path="school.country"></sl-grid-column>
</sl-grid>
`;
}
};