Skip to content

Commit

Permalink
fix: block cell nav events w internal focus queue active (#6754)
Browse files Browse the repository at this point in the history
* block cell nav events

* Change files

* better default focus fn

* update to lates
  • Loading branch information
Stephane Comeau authored Aug 11, 2023
1 parent e0d4cb4 commit a38e6b1
Show file tree
Hide file tree
Showing 8 changed files with 257 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "block cell nav events",
"packageName": "@microsoft/fast-foundation",
"email": "[email protected]",
"dependentChangeType": "prerelease"
}
7 changes: 5 additions & 2 deletions packages/web-components/fast-foundation/docs/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,12 @@ export function checkboxTemplate<T extends FASTCheckbox>(options?: CheckboxOptio

// @public
export interface ColumnDefinition {
cellFocusTargetCallback?: (cell: FASTDataGridCell) => HTMLElement;
cellFocusTargetCallback?: (cell: FASTDataGridCell) => HTMLElement | null;
cellInternalFocusQueue?: boolean;
cellTemplate?: ViewTemplate | SyntheticViewTemplate;
columnDataKey: string;
gridColumn?: string;
headerCellFocusTargetCallback?: (cell: FASTDataGridCell) => HTMLElement;
headerCellFocusTargetCallback?: (cell: FASTDataGridCell) => HTMLElement | null;
headerCellInternalFocusQueue?: boolean;
headerCellTemplate?: ViewTemplate | SyntheticViewTemplate;
isRowHeader?: boolean;
Expand Down Expand Up @@ -412,6 +412,9 @@ export const DayFormat: {
// @public
export type DayFormat = ValuesOf<typeof DayFormat>;

// @public (undocumented)
export const defaultCellFocusTargetCallback: (cell: FASTDataGridCell) => HTMLElement | null;

// Warning: (ae-different-release-tags) This symbol has another declaration with a different release tag
// Warning: (ae-internal-mixed-release-tag) Mixed release tags are not allowed for "DelegatesARIAButton" because one of its declarations is marked as @internal
//
Expand Down
46 changes: 46 additions & 0 deletions packages/web-components/fast-foundation/src/data-grid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ export const myDataGrid = DataGrid.compose({

<hr/>

### Functions

| Name | Description | Parameters | Return |
| -------------------------------- | ----------- | ------------------------ | --------------------- |
| `defaultCellFocusTargetCallback` | | `cell: FASTDataGridCell` | `HTMLElement or null` |

<hr/>



### class: `FASTDataGridRow`
Expand Down Expand Up @@ -281,6 +289,44 @@ export const myDataGrid = DataGrid.compose({
<hr/>



### class: `ComplexCell`

#### Superclass

| Name | Module | Package |
| ------------- | ------ | ----------------------- |
| `FASTElement` | | @microsoft/fast-element |

#### Fields

| Name | Privacy | Type | Default | Description | Inherited From |
| --------------- | ------- | ------------------- | ------- | ----------- | -------------- |
| `buttonA` | public | `HTMLButtonElement` | | | |
| `buttonB` | public | `HTMLButtonElement` | | | |
| `handleFocus` | public | | | | |
| `handleKeyDown` | public | | | | |

<hr/>

### Variables

| Name | Description | Type |
| ------------------- | ----------- | ---- |
| `complexCellStyles` | | |

<hr/>

### Functions

| Name | Description | Parameters | Return |
| --------------------- | ----------- | ---------- | ------------------------ |
| `registerComplexCell` | | | |
| `complexCellTemplate` | | | `ElementViewTemplate<T>` |

<hr/>


## Additional resources

* [Component explorer examples](https://explore.fast.design/components/fast-data-grid)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,19 @@ import {
eventFocusIn,
eventFocusOut,
eventKeyDown,
keyArrowDown,
keyArrowLeft,
keyArrowRight,
keyArrowUp,
keyEnd,
keyEnter,
keyEscape,
keyFunction2,
keyHome,
keyPageDown,
keyPageUp,
} from "@microsoft/fast-web-utilities";
import { isFocusable } from "tabbable";
import type { ColumnDefinition } from "./data-grid.js";
import { DataGridCellTypes } from "./data-grid.options.js";

Expand Down Expand Up @@ -35,6 +44,18 @@ const defaultHeaderCellContentsTemplate: ViewTemplate<FASTDataGridCell> = html`
</template>
`;

// basic focusTargetCallback that returns the first child of the cell
export const defaultCellFocusTargetCallback = (
cell: FASTDataGridCell
): HTMLElement | null => {
for (let i = 0; i < cell.children.length; i++) {
if (isFocusable(cell.children[i])) {
return cell.children[i] as HTMLElement;
}
}
return null;
};

/**
* A Data Grid Cell Custom HTML Element.
*
Expand Down Expand Up @@ -151,7 +172,7 @@ export class FASTDataGridCell extends FASTElement {
"function"
) {
// move focus to the focus target
const focusTarget: HTMLElement =
const focusTarget: HTMLElement | null =
this.columnDefinition.headerCellFocusTargetCallback(this);
if (focusTarget !== null) {
focusTarget.focus();
Expand All @@ -166,7 +187,7 @@ export class FASTDataGridCell extends FASTElement {
typeof this.columnDefinition.cellFocusTargetCallback === "function"
) {
// move focus to the focus target
const focusTarget: HTMLElement =
const focusTarget: HTMLElement | null =
this.columnDefinition.cellFocusTargetCallback(this);
if (focusTarget !== null) {
focusTarget.focus();
Expand All @@ -184,25 +205,37 @@ export class FASTDataGridCell extends FASTElement {
}
}

private hasInternalFocusQueue(): boolean {
if (this.columnDefinition === null) {
return false;
}
if (
(this.cellType === DataGridCellTypes.default &&
this.columnDefinition.cellInternalFocusQueue) ||
(this.cellType === DataGridCellTypes.columnHeader &&
this.columnDefinition.headerCellInternalFocusQueue)
) {
return true;
}
return false;
}

public handleKeydown(e: KeyboardEvent): void {
// if the cell does not have an internal focus queue we can ignore keystrokes
if (
e.defaultPrevented ||
this.columnDefinition === null ||
(this.cellType === DataGridCellTypes.default &&
this.columnDefinition.cellInternalFocusQueue !== true) ||
(this.cellType === DataGridCellTypes.columnHeader &&
this.columnDefinition.headerCellInternalFocusQueue !== true)
!this.hasInternalFocusQueue()
) {
return;
}

const rootActiveElement: Element | null = this.getRootActiveElement();

switch (e.key) {
case keyEnter:
case keyFunction2:
if (
this.contains(document.activeElement) &&
document.activeElement !== this
) {
if (this.contains(rootActiveElement) && rootActiveElement !== this) {
return;
}

Expand All @@ -212,7 +245,7 @@ export class FASTDataGridCell extends FASTElement {
this.columnDefinition.headerCellFocusTargetCallback !==
undefined
) {
const focusTarget: HTMLElement =
const focusTarget: HTMLElement | null =
this.columnDefinition.headerCellFocusTargetCallback(this);
if (focusTarget !== null) {
focusTarget.focus();
Expand All @@ -223,7 +256,7 @@ export class FASTDataGridCell extends FASTElement {

default:
if (this.columnDefinition.cellFocusTargetCallback !== undefined) {
const focusTarget: HTMLElement =
const focusTarget: HTMLElement | null =
this.columnDefinition.cellFocusTargetCallback(this);
if (focusTarget !== null) {
focusTarget.focus();
Expand All @@ -235,15 +268,38 @@ export class FASTDataGridCell extends FASTElement {
break;

case keyEscape:
if (
this.contains(document.activeElement) &&
document.activeElement !== this
) {
if (this.contains(rootActiveElement) && rootActiveElement !== this) {
this.focus();
e.preventDefault();
}
break;

// stop any unhandled grid nav events that may bubble from the cell
// when internal navigation is active.
// note: preventDefault would also block arrow keys in input elements
case keyArrowDown:
case keyArrowLeft:
case keyArrowRight:
case keyArrowUp:
case keyEnd:
case keyHome:
case keyPageDown:
case keyPageUp:
if (this.contains(rootActiveElement) && rootActiveElement !== this) {
e.stopPropagation();
}
break;
}
}

private getRootActiveElement(): Element | null {
const rootNode = this.getRootNode();

if (rootNode instanceof ShadowRoot) {
return rootNode.activeElement;
}

return document.activeElement;
}

private updateCellView(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export interface ColumnDefinition {
* focus directly to the checkbox.
* When headerCellInternalFocusQueue is true this function is called when the user hits Enter or F2
*/
headerCellFocusTargetCallback?: (cell: FASTDataGridCell) => HTMLElement;
headerCellFocusTargetCallback?: (cell: FASTDataGridCell) => HTMLElement | null;

/**
* cell template
Expand All @@ -98,7 +98,7 @@ export interface ColumnDefinition {
* When cellInternalFocusQueue is true this function is called when the user hits Enter or F2
*/

cellFocusTargetCallback?: (cell: FASTDataGridCell) => HTMLElement;
cellFocusTargetCallback?: (cell: FASTDataGridCell) => HTMLElement | null;

/**
* Whether this column is the row header
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { css } from "@microsoft/fast-element";
import { FASTDataGrid } from "../data-grid.js";
import { dataGridTemplate } from "../data-grid.template.js";
import { registerComplexCell } from "./examples/complex-cell.js";

const styles = css`
:host {
Expand All @@ -19,3 +20,5 @@ FASTDataGrid.define({
}),
styles,
});

registerComplexCell();
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { html } from "@microsoft/fast-element";
import type { Meta, Story, StoryArgs } from "../../__test__/helpers.js";
import { renderComponent } from "../../__test__/helpers.js";
import { defaultCellFocusTargetCallback } from "../data-grid-cell.js";
import {
DataGridSelectionBehavior,
DataGridSelectionMode,
Expand Down Expand Up @@ -108,3 +109,52 @@ DataGridColumnDefinitions.args = {
{ columnDataKey: "item2" },
],
};

const editCellTemplate = html`
<template>
<fast-text-field
tabIndex="-1"
value="${x => x.rowData[x.columnDefinition.columnDataKey]}"
></fast-text-field>
</template>
`;

const checkboxCellTemplate = html`
<template>
<fast-checkbox>${x => x.rowData[x.columnDefinition.columnDataKey]}</fast-checkbox>
</template>
`;

const complexCellTemplate = html`
<template>
<complex-cell
tabIndex="-1"
></complexCell>
</template>
`;

export const DataGridEditBoxes: Story<FASTDataGrid> = renderComponent(storyTemplate).bind(
{}
);
DataGridEditBoxes.args = {
columnDefinitions: [
{ columnDataKey: "rowId" },
{
columnDataKey: "item1",
cellTemplate: checkboxCellTemplate,
cellFocusTargetCallback: defaultCellFocusTargetCallback,
},
{
columnDataKey: "item2",
cellInternalFocusQueue: true,
cellTemplate: editCellTemplate,
cellFocusTargetCallback: defaultCellFocusTargetCallback,
},
{
columnDataKey: "item3",
cellInternalFocusQueue: true,
cellTemplate: complexCellTemplate,
cellFocusTargetCallback: defaultCellFocusTargetCallback,
},
],
};
Loading

0 comments on commit a38e6b1

Please sign in to comment.