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
18 changes: 16 additions & 2 deletions packages/calcite-components/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ import { StepperItemMessages } from "./components/stepper-item/assets/stepper-it
import { TabID, TabLayout, TabPosition } from "./components/tabs/interfaces";
import { TabChangeEventDetail, TabCloseEventDetail } from "./components/tab/interfaces";
import { TabTitleMessages } from "./components/tab-title/assets/tab-title/t9n";
import { RowType, TableLayout, TableRowFocusEvent } from "./components/table/interfaces";
import { RowType, TableInteractionMode, TableLayout, TableRowFocusEvent } from "./components/table/interfaces";
import { TableMessages } from "./components/table/assets/table/t9n";
import { TableCellMessages } from "./components/table-cell/assets/table-cell/t9n";
import { TableHeaderMessages } from "./components/table-header/assets/table-header/t9n";
Expand Down Expand Up @@ -165,7 +165,7 @@ export { StepperItemMessages } from "./components/stepper-item/assets/stepper-it
export { TabID, TabLayout, TabPosition } from "./components/tabs/interfaces";
export { TabChangeEventDetail, TabCloseEventDetail } from "./components/tab/interfaces";
export { TabTitleMessages } from "./components/tab-title/assets/tab-title/t9n";
export { RowType, TableLayout, TableRowFocusEvent } from "./components/table/interfaces";
export { RowType, TableInteractionMode, TableLayout, TableRowFocusEvent } from "./components/table/interfaces";
export { TableMessages } from "./components/table/assets/table/t9n";
export { TableCellMessages } from "./components/table-cell/assets/table-cell/t9n";
export { TableHeaderMessages } from "./components/table-header/assets/table-header/t9n";
Expand Down Expand Up @@ -4648,6 +4648,10 @@ export namespace Components {
* When `true`, number values are displayed with a group separator corresponding to the language and country format.
*/
"groupSeparator": boolean;
/**
* When `"interactive"`, allows focus and keyboard navigation of `table-header`s and `table-cell`s. When `"static"`, prevents focus and keyboard navigation of `table-header`s and `table-cell`s when assistive technologies are not active. Selection affordances and slotted content within `table-cell`s remain focusable.
*/
"interactionMode": TableInteractionMode;
/**
* Specifies the layout of the component.
*/
Expand Down Expand Up @@ -4708,6 +4712,7 @@ export namespace Components {
* When true, prevents user interaction. Notes: This prop should use the
*/
"disabled": boolean;
"interactionMode": TableInteractionMode;
"lastCell": boolean;
/**
* Use this property to override individual strings used by the component.
Expand Down Expand Up @@ -4752,6 +4757,7 @@ export namespace Components {
* A heading to display above description content.
*/
"heading": string;
"interactionMode": TableInteractionMode;
"lastCell": boolean;
/**
* Use this property to override individual strings used by the component.
Expand Down Expand Up @@ -4787,6 +4793,7 @@ export namespace Components {
* When `true`, interaction is prevented and the component is displayed with lower opacity.
*/
"disabled": boolean;
"interactionMode": TableInteractionMode;
"lastVisibleRow": boolean;
"numbered": boolean;
"positionAll": number;
Expand Down Expand Up @@ -12143,6 +12150,10 @@ declare namespace LocalJSX {
* When `true`, number values are displayed with a group separator corresponding to the language and country format.
*/
"groupSeparator"?: boolean;
/**
* When `"interactive"`, allows focus and keyboard navigation of `table-header`s and `table-cell`s. When `"static"`, prevents focus and keyboard navigation of `table-header`s and `table-cell`s when assistive technologies are not active. Selection affordances and slotted content within `table-cell`s remain focusable.
*/
"interactionMode"?: TableInteractionMode;
/**
* Specifies the layout of the component.
*/
Expand Down Expand Up @@ -12212,6 +12223,7 @@ declare namespace LocalJSX {
* When true, prevents user interaction. Notes: This prop should use the
*/
"disabled"?: boolean;
"interactionMode"?: TableInteractionMode;
"lastCell"?: boolean;
/**
* Use this property to override individual strings used by the component.
Expand Down Expand Up @@ -12252,6 +12264,7 @@ declare namespace LocalJSX {
* A heading to display above description content.
*/
"heading"?: string;
"interactionMode"?: TableInteractionMode;
"lastCell"?: boolean;
/**
* Use this property to override individual strings used by the component.
Expand Down Expand Up @@ -12283,6 +12296,7 @@ declare namespace LocalJSX {
* When `true`, interaction is prevented and the component is displayed with lower opacity.
*/
"disabled"?: boolean;
"interactionMode"?: TableInteractionMode;
"lastVisibleRow"?: boolean;
"numbered"?: boolean;
"onCalciteInternalTableRowFocusRequest"?: (event: CalciteTableRowCustomEvent<TableRowFocusEvent>) => void;
Expand Down
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",
staticCell: "static-cell",
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,18 @@
}

td {
@apply text-start focus-base align-middle text-color-1 whitespace-normal;
@apply text-start align-middle text-color-1 whitespace-normal;
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 {
@apply focus-inset;
&:not(.static-cell) {
@apply focus-base;
&:focus {
@apply focus-inset;
}
}
padding: var(--calcite-internal-table-cell-padding);
}

td.last-cell {
Expand All @@ -56,8 +59,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 @@ -24,7 +24,7 @@ import {
import { connectLocalized, disconnectLocalized, LocalizedComponent } from "../../utils/locale";
import { TableCellMessages } from "./assets/table-cell/t9n";
import { CSS } from "./resources";
import { RowType } from "../table/interfaces";
import { RowType, TableInteractionMode } from "../table/interfaces";
import { getElementDir } from "../../utils/dom";
import { CSS_UTILITY } from "../../utils/resources";

Expand Down Expand Up @@ -58,6 +58,9 @@ export class TableCell
/** @internal */
@Prop() disabled: boolean;

/** @internal */
@Prop() interactionMode: TableInteractionMode = "interactive";

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

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

render(): VNode {
const dir = getElementDir(this.el);
const staticCell =
this.disabled ||
(this.interactionMode === "static" &&
(!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.staticCell]: staticCell,
}}
colSpan={this.colSpan}
onBlur={this.onContainerBlur}
onFocus={this.onContainerFocus}
role="gridcell"
rowSpan={this.rowSpan}
tabIndex={this.disabled ? -1 : 0}
tabIndex={staticCell ? -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",
staticCell: "static-cell",
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,19 @@
}

th {
@apply text-color-1 focus-base text-start font-medium align-top whitespace-normal;
@apply text-color-1 text-start font-medium align-top whitespace-normal;
font-size: var(--calcite-internal-table-cell-font-size);
border-inline-end: 1px solid var(--calcite-internal-table-header-border-color);
border-block-end: 1px solid var(--calcite-internal-table-header-border-color);
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 {
@apply focus-inset;

&:not(.static-cell) {
@apply focus-base;
&:not(.static-cell):focus-within {
@apply focus-inset;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { connectLocalized, disconnectLocalized, LocalizedComponent } from "../..
import { Alignment, Scale, SelectionMode } from "../interfaces";
import { TableHeaderMessages } from "./assets/table-header/t9n";
import { CSS } from "./resources";
import { RowType } from "../table/interfaces";
import { RowType, TableInteractionMode } from "../table/interfaces";
import { getIconScale } from "../../utils/component";

@Component({
Expand Down Expand Up @@ -47,6 +47,9 @@ export class TableHeader implements LocalizedComponent, LoadableComponent, T9nCo
/** Specifies the number of rows the component should span. */
@Prop({ reflect: true }) rowSpan: number;

/** @internal */
@Prop() interactionMode: TableInteractionMode = "interactive";

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

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 staticCell = this.interactionMode === "static" && !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.staticCell]: staticCell,
[CSS.lastCell]: this.lastCell,
}}
colSpan={this.colSpan}
role="columnheader"
rowSpan={this.rowSpan}
scope={scope}
tabIndex={0}
tabIndex={this.selectionCell ? 0 : staticCell ? -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
16 changes: 12 additions & 4 deletions packages/calcite-components/src/components/table-row/table-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
import { LocalizedComponent } from "../../utils/locale";
import { Scale, SelectionMode } from "../interfaces";
import { focusElementInGroup, FocusElementInGroupDestination } from "../../utils/dom";
import { RowType, TableRowFocusEvent } from "../table/interfaces";
import { RowType, TableInteractionMode, TableRowFocusEvent } from "../table/interfaces";
import { isActivationKey } from "../../utils/key";
import {
connectInteractive,
Expand Down Expand Up @@ -51,6 +51,9 @@ export class TableRow implements InteractiveComponent, LocalizedComponent {
/** @internal */
@Prop({ mutable: true }) cellCount: number;

/** @internal */
@Prop() interactionMode: TableInteractionMode = "interactive";

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

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

private keyDownHandler(event: KeyboardEvent): void {
private keyDownHandler = (event: KeyboardEvent): void => {
if (this.interactionMode !== "interactive") {
return;
}
const el = event.target as HTMLCalciteTableCellElement | HTMLCalciteTableHeaderElement;
const key = event.key;
const isControl = event.ctrlKey;
Expand Down Expand Up @@ -249,7 +256,7 @@ export class TableRow implements InteractiveComponent, LocalizedComponent {
break;
}
}
}
};

private emitTableRowFocusRequest = (
cellPosition: number,
Expand Down Expand Up @@ -284,6 +291,7 @@ export class TableRow implements InteractiveComponent, LocalizedComponent {

if (cells.length > 0) {
cells?.forEach((cell: HTMLCalciteTableCellElement | HTMLCalciteTableHeaderElement, index) => {
cell.interactionMode = this.interactionMode;
cell.positionInRow = index + 1;
cell.parentRowType = this.rowType;
cell.parentRowIsSelected = this.selected;
Expand Down Expand Up @@ -385,7 +393,7 @@ 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={this.keyDownHandler}
// 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export interface TableRowFocusEvent {
export type RowType = "head" | "body" | "foot";

export type TableLayout = "auto" | "fixed";

export type TableInteractionMode = "interactive" | "static";
Loading
Loading