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

feat(table): Add interactionMode property to control focus behavior #8686

Merged
merged 10 commits into from
Feb 12, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export const CSS = {
selectedCell: "selected-cell",
assistiveText: "assistive-text",
lastCell: "last-cell",
nonInteractive: "non-interactive",
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: should this and related styles reflect the updated name?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to match other css cell modifiers 👍

};
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ td {
background: var(--calcite-internal-table-cell-background);
font-size: var(--calcite-internal-table-cell-font-size);
border-inline-end: 1px solid var(--calcite-color-border-3);
padding: var(--calcite-internal-table-cell-padding);

&:focus {
&:not(.non-interactive):focus {
@apply focus-inset;
}
padding: var(--calcite-internal-table-cell-padding);
}

td.last-cell {
Expand All @@ -56,8 +56,11 @@ td.last-cell {
}

.selection-cell {
@apply cursor-pointer text-color-3;
@apply text-color-3;
inset-inline-start: 2rem;
&:not(.footer-cell) {
@apply cursor-pointer;
}
}

.selected-cell:not(.number-cell):not(.footer-cell) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ export class TableCell
/** @internal */
@Prop() lastCell: boolean;

/** @internal */
@Prop() nonInteractive = false;
macandcheese marked this conversation as resolved.
Show resolved Hide resolved

/** @internal */
@Prop() numberCell: boolean;

Expand Down Expand Up @@ -212,6 +215,10 @@ export class TableCell

render(): VNode {
const dir = getElementDir(this.el);
const nonFocusable =
this.disabled ||
(this.nonInteractive &&
(!this.selectionCell || (this.selectionCell && this.parentRowType === "foot")));

return (
<Host>
Expand All @@ -225,13 +232,14 @@ export class TableCell
[CSS.selectedCell]: this.parentRowIsSelected,
[CSS.lastCell]: this.lastCell,
[CSS_UTILITY.rtl]: dir === "rtl",
[CSS.nonInteractive]: nonFocusable,
}}
colSpan={this.colSpan}
onBlur={this.onContainerBlur}
onFocus={this.onContainerFocus}
role="gridcell"
rowSpan={this.rowSpan}
tabIndex={this.disabled ? -1 : 0}
tabIndex={nonFocusable ? -1 : 0}
// eslint-disable-next-line react/jsx-sort-props -- ref should be last so node attrs/props are in sync (see https://github.com/Esri/calcite-design-system/pull/6530)
ref={(el) => (this.containerEl = el)}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export const CSS = {
active: "active",
selectedCell: "selected-cell",
lastCell: "last-cell",
nonInteractive: "non-interactive",
};
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ th {
padding-block: calc(var(--calcite-internal-table-cell-padding) * 1.5);
padding-inline: var(--calcite-internal-table-cell-padding);
background-color: var(--calcite-internal-table-header-background);
&:focus-within {

&:not(.non-interactive):focus-within {
@apply focus-inset;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export class TableHeader implements LocalizedComponent, LoadableComponent, T9nCo
/** @internal */
@Prop() lastCell: boolean;

/** @internal */
@Prop() nonInteractive = false;

/** @internal */
@Prop() numberCell = false;

Expand Down Expand Up @@ -205,7 +208,7 @@ export class TableHeader implements LocalizedComponent, LoadableComponent, T9nCo

const allSelected = this.selectedRowCount === this.bodyRowCount;
const selectionIcon = allSelected ? "check-square-f" : "check-square";

const nonFocusable = this.nonInteractive && !this.selectionCell;
return (
<Host>
<th
Expand All @@ -217,13 +220,14 @@ export class TableHeader implements LocalizedComponent, LoadableComponent, T9nCo
[CSS.selectionCell]: this.selectionCell,
[CSS.selectedCell]: this.parentRowIsSelected,
[CSS.multipleSelectionCell]: this.selectionMode === "multiple",
[CSS.nonInteractive]: nonFocusable,
[CSS.lastCell]: this.lastCell,
}}
colSpan={this.colSpan}
role="columnheader"
rowSpan={this.rowSpan}
scope={scope}
tabIndex={0}
tabIndex={this.selectionCell ? 0 : nonFocusable ? -1 : 0}
// eslint-disable-next-line react/jsx-sort-props -- ref should be last so node attrs/props are in sync (see https://github.com/Esri/calcite-design-system/pull/6530)
ref={(el) => (this.containerEl = el)}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export class TableRow implements InteractiveComponent, LocalizedComponent {
/** @internal */
@Prop() rowType: RowType;

/** @internal */
@Prop() nonInteractive = false;

/** @internal */
@Prop() numbered = false;

Expand Down Expand Up @@ -91,6 +94,7 @@ export class TableRow implements InteractiveComponent, LocalizedComponent {
@Watch("scale")
@Watch("selected")
@Watch("selectedRowCount")
@Watch("nonInteractive")
handleCellChanges(): void {
if (this.tableRowEl && this.rowCells.length > 0) {
this.updateCells();
Expand Down Expand Up @@ -284,6 +288,7 @@ export class TableRow implements InteractiveComponent, LocalizedComponent {

if (cells.length > 0) {
cells?.forEach((cell: HTMLCalciteTableCellElement | HTMLCalciteTableHeaderElement, index) => {
cell.nonInteractive = this.nonInteractive;
cell.positionInRow = index + 1;
cell.parentRowType = this.rowType;
cell.parentRowIsSelected = this.selected;
Expand Down Expand Up @@ -385,7 +390,11 @@ export class TableRow implements InteractiveComponent, LocalizedComponent {
aria-rowindex={this.positionAll + 1}
aria-selected={this.selected}
class={{ [CSS.lastVisibleRow]: this.lastVisibleRow }}
onKeyDown={(event) => this.keyDownHandler(event)}
onKeyDown={(event) => {
if (!this.nonInteractive) {
this.keyDownHandler(event);
}
}}
// eslint-disable-next-line react/jsx-sort-props -- ref should be last so node attrs/props are in sync (see https://github.com/Esri/calcite-design-system/pull/6530)
ref={(el) => (this.tableRowEl = el)}
>
Expand Down
128 changes: 128 additions & 0 deletions packages/calcite-components/src/components/table/table.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,45 @@ describe("calcite-table", () => {
);
});

describe("is accessible with pagination and non-interactive", () => {
accessible(
html`<calcite-table page-size="4" caption="Simple table" non-interactive>
<calcite-table-row slot=${SLOTS.tableHeader}>
<calcite-table-header heading="Heading" description="Description"></calcite-table-header>
<calcite-table-header heading="Heading" description="Description"></calcite-table-header>
</calcite-table-row>
<calcite-table-row>
<calcite-table-cell>cell</calcite-table-cell>
<calcite-table-cell>cell</calcite-table-cell>
</calcite-table-row>
<calcite-table-row>
<calcite-table-cell>cell</calcite-table-cell>
<calcite-table-cell>cell</calcite-table-cell>
</calcite-table-row>
<calcite-table-row>
<calcite-table-cell>cell</calcite-table-cell>
<calcite-table-cell>cell</calcite-table-cell>
</calcite-table-row>
<calcite-table-row>
<calcite-table-cell>cell</calcite-table-cell>
<calcite-table-cell>cell</calcite-table-cell>
</calcite-table-row>
<calcite-table-row>
<calcite-table-cell>cell</calcite-table-cell>
<calcite-table-cell>cell</calcite-table-cell>
</calcite-table-row>
<calcite-table-row>
<calcite-table-cell>cell</calcite-table-cell>
<calcite-table-cell>cell</calcite-table-cell>
</calcite-table-row>
<calcite-table-row>
<calcite-table-cell>cell</calcite-table-cell>
<calcite-table-cell>cell</calcite-table-cell>
</calcite-table-row>
</calcite-table>`,
);
});

describe("is accessible with pagination and selection mode", () => {
accessible(
html`<calcite-table page-size="4" selection-mode="multiple" caption="Simple table">
Expand Down Expand Up @@ -2464,6 +2503,95 @@ describe("keyboard navigation", () => {
),
).toEqual({ "0": CELL_CSS.footerCell, "1": CSS.numberCell });
});

it("navigates correctly when number and selection column present numbered and non-interactive - only focusing selection cells", async () => {
const page = await newE2EPage();
await page.setContent(
html`<calcite-table numbered selection-mode="multiple" caption="Simple table" non-interactive>
<calcite-table-row id="row-head" slot=${SLOTS.tableHeader}>
<calcite-table-header id="head-1a" heading="Heading" description="Description"></calcite-table-header>
<calcite-table-header id="head-1b" heading="Heading" description="Description"></calcite-table-header>
</calcite-table-row>
<calcite-table-row id="row-1">
<calcite-table-cell id="cell-1a">cell</calcite-table-cell>
<calcite-table-cell id="cell-2b">cell</calcite-table-cell>
</calcite-table-row>
<calcite-table-row id="row-2">
<calcite-table-cell id="cell-2a">cell</calcite-table-cell>
<calcite-table-cell id="cell-2b">cell</calcite-table-cell>
</calcite-table-row>
<calcite-table-row id="row-3">
<calcite-table-cell id="cell-3a">cell</calcite-table-cell>
<calcite-table-cell id="cell-3b">cell</calcite-table-cell>
</calcite-table-row>
<calcite-table-row slot=${SLOTS.tableFooter} id="row-foot">
<calcite-table-cell id="foot-1a">foot</calcite-table-cell>
<calcite-table-cell id="foot-1b">foot</calcite-table-cell>
</calcite-table-row>
</calcite-table>`,
);

const rowHead = await page.find("#row-head");
const row1 = await page.find("#row-1");
const row2 = await page.find("#row-2");
const row3 = await page.find("#row-3");

await page.keyboard.press("Tab");
await page.waitForChanges();

expect(
await page.$eval(
`#${rowHead.id}`,
(el) => el.shadowRoot?.activeElement.shadowRoot?.querySelector("th").classList,
),
).toEqual({ "0": CSS.selectionCell, "1": CSS.multipleSelectionCell });
macandcheese marked this conversation as resolved.
Show resolved Hide resolved

await page.keyboard.press("ArrowRight");
await page.waitForChanges();

await page.keyboard.press("ArrowLeft");
await page.waitForChanges();
expect(
await page.$eval(
`#${rowHead.id}`,
(el) => el.shadowRoot?.activeElement.shadowRoot?.querySelector("th").classList,
),
).toEqual({ "0": CSS.selectionCell, "1": CSS.multipleSelectionCell });

await page.keyboard.press("ArrowRight");
await page.waitForChanges();

expect(
await page.$eval(
`#${rowHead.id}`,
(el) => el.shadowRoot?.activeElement.shadowRoot?.querySelector("th").classList,
),
).toEqual({ "0": CSS.selectionCell, "1": CSS.multipleSelectionCell });

await page.keyboard.press("Tab");
await page.waitForChanges();
expect(
await page.$eval(`#${row1.id}`, (el) => el.shadowRoot?.activeElement.shadowRoot?.querySelector("td").classList),
).toEqual({ "0": CSS.selectionCell });

await page.keyboard.press("Tab");
await page.waitForChanges();
expect(
await page.$eval(`#${row2.id}`, (el) => el.shadowRoot?.activeElement.shadowRoot?.querySelector("td").classList),
).toEqual({ "0": CSS.selectionCell });

await page.keyboard.press("Tab");
await page.waitForChanges();
expect(
await page.$eval(`#${row3.id}`, (el) => el.shadowRoot?.activeElement.shadowRoot?.querySelector("td").classList),
).toEqual({ "0": CSS.selectionCell });

await page.keyboard.press("ArrowUp");
await page.waitForChanges();
expect(
await page.$eval(`#${row3.id}`, (el) => el.shadowRoot?.activeElement.shadowRoot?.querySelector("td").classList),
).toEqual({ "0": CSS.selectionCell });
});
});

// Borrowed from Dropdown until a generic utility is set up.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const simple = (): string =>
${boolean("numbered", false)}
${boolean("bordered", false)}
${boolean("striped", false)}
${boolean("non-interactive", false)}
caption="Simple table"
>
<calcite-table-row slot="table-header">
Expand Down
5 changes: 5 additions & 0 deletions packages/calcite-components/src/components/table/table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export class Table implements LocalizedComponent, LoadableComponent, T9nComponen
/** Specifies the layout of the component. */
@Prop({ reflect: true }) layout: TableLayout = "auto";

/** When `true`, does not support focus of cells or allow navigation with arrow keys. Content slotted in Table Cell components remains focusable. */
@Prop({ reflect: true }) nonInteractive = false;

/** When `true`, displays the position of the row in numeric form. */
@Prop({ reflect: true }) numbered = false;

Expand Down Expand Up @@ -103,6 +106,7 @@ export class Table implements LocalizedComponent, LoadableComponent, T9nComponen
@Prop({ reflect: true }) striped = false;

@Watch("groupSeparator")
@Watch("nonInteractive")
@Watch("numbered")
@Watch("numberingSystem")
@Watch("pageSize")
Expand Down Expand Up @@ -328,6 +332,7 @@ export class Table implements LocalizedComponent, LoadableComponent, T9nComponen
});

allRows?.forEach((row) => {
row.nonInteractive = this.nonInteractive;
row.selectionMode = this.selectionMode;
row.bodyRowCount = bodyRows?.length;
row.positionAll = allRows?.indexOf(row);
Expand Down
Loading
Loading