Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "office-ui-fabric-react",
"comment": "[Focus] Enable focus forceIntoFirstElement parameter",
"type": "patch"
}
],
"packageName": "office-ui-fabric-react",
"email": "law@microsoft.com"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import * as React from 'react';
import * as renderer from 'react-test-renderer';
import * as ReactTestUtils from 'react-dom/test-utils';
import { mount } from 'enzyme';

import {
DetailsList
} from './DetailsList';

import {
IDetailsList,
IColumn
} from './DetailsList.types';

// Populate mock items for testing
function mockItems(count: number): any {
const items = [];

for (let i = 0; i < count; i++) {
items.push({
key: i,
name: 'Item ' + i,
value: i
});
}

return items;
}

describe('DetailsList', () => {
it('renders List correctly', () => {
DetailsList.prototype.componentDidMount = jest.fn();

const component = renderer.create(
<DetailsList
items={ mockItems(5) }
// tslint:disable-next-line:jsx-no-lambda
onRenderRow={ () => null }
skipViewportMeasures={ true }
// tslint:disable-next-line:jsx-no-lambda
onShouldVirtualize={ () => false }
/>
);
const tree = component.toJSON();
expect(tree).toMatchSnapshot();
});

it('focuses row by index', () => {
jest.useFakeTimers();

let component: any;
const wrapper = mount(
<DetailsList
items={ mockItems(5) }
// tslint:disable-next-line:jsx-no-lambda
componentRef={ ref => component = ref }
skipViewportMeasures={ true }
// tslint:disable-next-line:jsx-no-lambda
onShouldVirtualize={ () => false }
/>);

expect(component).toBeDefined();
(component as IDetailsList).focusIndex(2);
setTimeout(() => {
expect(document.activeElement.className.split(' ')).toContain('ms-DetailsRow');
expect(document.activeElement.textContent).toEqual('2');
}, 0);
jest.runOnlyPendingTimers();
});

it('focuses into row element', () => {
const onRenderColumn = (item: any, index: number, column: IColumn) => {
let value = (item && column && column.fieldName) ? item[column.fieldName] : '';
if (value === null || value === undefined) {
value = '';
}
console.log('Rendered column');
return (
<div className={ 'test-column' } data-is-focusable={ true } >
{ value }
</div>
);
};

jest.useFakeTimers();

let component: any;
const wrapper = mount(
<DetailsList
items={ mockItems(5) }
// tslint:disable-next-line:jsx-no-lambda
componentRef={ ref => component = ref }
skipViewportMeasures={ true }
// tslint:disable-next-line:jsx-no-lambda
onShouldVirtualize={ () => false }
onRenderItemColumn={ onRenderColumn }
/>);

expect(component).toBeDefined();
(component as IDetailsList).focusIndex(2);
setTimeout(() => {
expect(document.activeElement.className.split(' ')).toContain('ms-DetailsRow');
expect(document.activeElement.textContent).toEqual('2');
}, 0);
jest.runOnlyPendingTimers();

(component as IDetailsList).focusIndex(2, true);
setTimeout(() => {
expect(document.activeElement.className.split(' ')).toContain('test-column');
expect(document.activeElement.textContent).toEqual('2');
}, 0);
jest.runOnlyPendingTimers();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,23 @@ export class DetailsList extends BaseComponent<IDetailsListProps, IDetailsListSt
this._groupedList && this._groupedList.scrollToIndex(index, measureItem);
}

public focusIndex(
Copy link
Member

Choose a reason for hiding this comment

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

I see a public API being added, but no change to a .types file.

It would also be great to use enzyme and write some tests to cover these things.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

index: number,
forceIntoFirstElement: boolean = false,
measureItem?: (itemIndex: number) => number): void {

const item = this.props.items[index];
if (item) {
this.scrollToIndex(index, measureItem);

const itemKey = this._getItemKey(item, index);
const row = this._activeRows[itemKey];
if (row) {
this._setFocusToRow(row, forceIntoFirstElement);
}
}
}

public componentWillUnmount() {
if (this._dragDropHelper) {
this._dragDropHelper.dispose();
Expand Down Expand Up @@ -537,12 +554,12 @@ export class DetailsList extends BaseComponent<IDetailsListProps, IDetailsListSt
}
}

private _setFocusToRow(row: DetailsRow) {
private _setFocusToRow(row: DetailsRow, forceIntoFirstElement: boolean = false) {
if (this._selectionZone) {
this._selectionZone.ignoreNextFocus();
}
this._async.setTimeout(() => {
row.focus();
row.focus(forceIntoFirstElement);
}, 0);
}

Expand Down Expand Up @@ -679,7 +696,7 @@ export class DetailsList extends BaseComponent<IDetailsListProps, IDetailsListSt
let totalWidth = 0; // offset because we have one less inner padding.
const availableWidth = viewportWidth - (outerPadding + rowCheckWidth + groupExpandWidth);
const adjustedColumns: IColumn[] = newColumns.map((column, i) => {
const newColumn = assign(
const newColumn = assign(
{},
column,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ export interface IDetailsList extends IList {
* call this to force a re-evaluation. Be aware that this can be an expensive operation and should be done sparingly.
*/
forceUpdate: () => void;

/**
* Scroll to and focus the item at the given index. focusIndex will call scrollToIndex on the specified index.
*
* @param index Index of item to scroll to
* @param forceIntoFirstElement If true, focus will be set to the first focusable child element of the item rather
* than the item itself.
* @param measureItem Optional callback to measure the height of an individual item
*/
focusIndex: (index: number, forceIntoFirstElement?: boolean, measureItem?: (itemIndex: number) => number) => void;
}

export interface IDetailsListProps extends React.Props<DetailsList>, IWithViewportProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,8 @@ export class DetailsRow extends BaseComponent<IDetailsRowProps, IDetailsRowState
});
}

public focus(): boolean {
return !!this._focusZone && this._focusZone.focus();
public focus(forceIntoFirstElement: boolean = false): boolean {
return !!this._focusZone && this._focusZone.focus(forceIntoFirstElement);
}

protected _onRenderCheck(props: IDetailsRowCheckProps) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,4 @@ export class DetailsRowFields extends BaseComponent<IDetailsRowFieldsProps, IDet

return value;
}

}
Loading