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

Add tooltip to ellipsized table header #1304

Merged
merged 7 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
@@ -0,0 +1,7 @@
{
m-akinc marked this conversation as resolved.
Show resolved Hide resolved
"type": "minor",
"comment": "Add tooltip to ellipsized table header",
"packageName": "@ni/nimble-components",
"email": "[email protected]",
"dependentChangeType": "patch"
}
27 changes: 26 additions & 1 deletion packages/nimble-components/src/table-column/base/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { attr, nullableNumberConverter } from '@microsoft/fast-element';
import {
attr,
nullableNumberConverter,
observable
} from '@microsoft/fast-element';
import { FoundationElement } from '@microsoft/fast-foundation';
import { TableColumnSortDirection } from '../../table/types';
import {
Expand Down Expand Up @@ -41,6 +45,17 @@ export abstract class TableColumn<
@attr({ attribute: 'sorting-disabled', mode: 'boolean' })
public sortingDisabled = false;

/** @internal */
@observable
public isValidContentAndHasOverflow = false;

/** @internal */
public headerSpan!: HTMLSpanElement;

/** @internal */
@observable
public contentSlot?: HTMLSlotElement;
rajsite marked this conversation as resolved.
Show resolved Hide resolved

public checkValidity(): boolean {
return this.columnInternals.validConfiguration;
}
Expand All @@ -49,6 +64,16 @@ export abstract class TableColumn<
return {};
}

/** @internal */
public get headerTextContent(): string {
return (
this.contentSlot
?.assignedNodes()
.map(node => node.textContent?.trim())
.join(' ') ?? ''
);
}

protected abstract getColumnInternalsOptions(): ColumnInternalsOptions;

protected sortDirectionChanged(): void {
Expand Down
18 changes: 15 additions & 3 deletions packages/nimble-components/src/table-column/base/template.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
import { html } from '@microsoft/fast-element';
import { html, ref } from '@microsoft/fast-element';
import type { TableColumn } from '.';

// prettier-ignore
export const template = html<TableColumn>`
<template slot="${x => x.columnInternals.uniqueId}">
<span class="header-content">
<slot></slot>
<span
${ref('headerSpan')}
class="header-content"
@mouseover="${x => {
x.isValidContentAndHasOverflow = !!x.headerTextContent
&& x.headerSpan.offsetWidth < x.headerSpan.scrollWidth;
}}"
@mouseout="${x => {
x.isValidContentAndHasOverflow = false;
}}"
title=${x => (x.isValidContentAndHasOverflow ? x.headerTextContent : null)}
>
<slot ${ref('contentSlot')}></slot>
</span>
</template>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,21 @@ export class TablePageObject<T extends TableRecord> {
return headers.item(columnIndex);
}

public getHeaderTitle(columnIndex: number): string {
const column = this.tableElement.columns[columnIndex];
return (
column?.shadowRoot!.firstElementChild?.getAttribute('title') ?? ''
);
}

public dispatchEventToHeader(
columnIndex: number,
event: Event
): boolean | undefined {
const column = this.tableElement.columns[columnIndex];
return column?.shadowRoot!.firstElementChild?.dispatchEvent(event);
}

public getHeaderRenderedWidth(columnIndex: number): number {
const headers = this.tableElement.shadowRoot!.querySelectorAll<TableHeader>(
'nimble-table-header'
Expand Down
37 changes: 36 additions & 1 deletion packages/nimble-components/src/table/tests/table.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const largeTableData = Array.from(Array(500), (_, i) => {
// prettier-ignore
async function setup(): Promise<Fixture<Table<SimpleTableRecord>>> {
return fixture<Table<SimpleTableRecord>>(
html`<nimble-table>
html`<nimble-table style="width: 700px">
<nimble-table-column-text id="first-column" field-name="stringData">stringData</nimble-table-column-text>
<nimble-table-column-text id="second-column" field-name="moreStringData">
<nimble-icon-check></nimble-icon-check>
Expand Down Expand Up @@ -187,6 +187,41 @@ describe('Table', () => {
expect(headerContent?.textContent).toEqual('foo');
});

it('sets title when header text is ellipsized', async () => {
m-akinc marked this conversation as resolved.
Show resolved Hide resolved
const headerContents = 'a very long value that should get ellipsized due to not fitting within the default header width';
await element.setData(simpleTableData);
await connect();
await waitForUpdatesAsync();
element.columns[0]!.textContent = headerContents;
pageObject.dispatchEventToHeader(0, new MouseEvent('mouseover'));
await waitForUpdatesAsync();
expect(pageObject.getHeaderTitle(0)).toBe(headerContents);
});

it('does not set title when header text is fully visible', async () => {
const headerContents = 'short value';
await element.setData(simpleTableData);
await connect();
await waitForUpdatesAsync();
element.columns[0]!.textContent = headerContents;
pageObject.dispatchEventToHeader(0, new MouseEvent('mouseover'));
await waitForUpdatesAsync();
expect(pageObject.getHeaderTitle(0)).toBe('');
});

it('removes title on mouseout of header', async () => {
const headerContents = 'a very long value that should get ellipsized due to not fitting within the default header width';
await element.setData(simpleTableData);
await connect();
await waitForUpdatesAsync();
element.columns[0]!.textContent = headerContents;
pageObject.dispatchEventToHeader(0, new MouseEvent('mouseover'));
await waitForUpdatesAsync();
pageObject.dispatchEventToHeader(0, new MouseEvent('mouseout'));
await waitForUpdatesAsync();
expect(pageObject.getHeaderTitle(0)).toBe('');
});

it('can set data before the element is connected', async () => {
await element.setData(simpleTableData);
await connect();
Expand Down