diff --git a/common/changes/office-ui-fabric-react/focuszone-align_2019-03-22-17-55.json b/common/changes/office-ui-fabric-react/focuszone-align_2019-03-22-17-55.json new file mode 100644 index 00000000000000..63f9f057d1977f --- /dev/null +++ b/common/changes/office-ui-fabric-react/focuszone-align_2019-03-22-17-55.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "FocusZone: focus alignment is now reset when first receiving focus (programatically or via `defaultActiveElement`.", + "type": "patch" + } + ], + "packageName": "office-ui-fabric-react", + "email": "dzearing@microsoft.com" +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/FocusZone/FocusZone.test.tsx b/packages/office-ui-fabric-react/src/components/FocusZone/FocusZone.test.tsx index 9b6041b04053ec..38dbbca03d3b5d 100644 --- a/packages/office-ui-fabric-react/src/components/FocusZone/FocusZone.test.tsx +++ b/packages/office-ui-fabric-react/src/components/FocusZone/FocusZone.test.tsx @@ -704,6 +704,77 @@ describe('FocusZone', () => { setRTL(false); }); + it('can focus correctly when receiving initial focus, bidirectionally', () => { + const component = ReactTestUtils.renderIntoDocument( +
+ + + + + + +
+ ); + + const focusZone = ReactDOM.findDOMNode(component as React.ReactInstance)!.firstChild as Element; + const buttonA = focusZone.querySelector('.a') as HTMLElement; + const buttonB = focusZone.querySelector('.b') as HTMLElement; + const buttonC = focusZone.querySelector('.c') as HTMLElement; + const buttonD = focusZone.querySelector('.d') as HTMLElement; + + // Set up a grid like so: + // A B + // C D + // + // We will focus B, and then down arrow, expecting D to be focused. + + setupElement(buttonA, { + clientRect: { + top: 0, + bottom: 20, + left: 0, + right: 30 + } + }); + + setupElement(buttonB, { + clientRect: { + top: 0, + bottom: 20, + left: 20, + right: 40 + } + }); + + setupElement(buttonC, { + clientRect: { + top: 20, + bottom: 40, + left: 0, + right: 20 + } + }); + + setupElement(buttonD, { + clientRect: { + top: 20, + bottom: 40, + left: 20, + right: 40 + } + }); + + // Focus the first button. + ReactTestUtils.Simulate.focus(buttonB); + expect(buttonA.getAttribute('tabindex')).toBe('-1'); + expect(buttonB.getAttribute('tabindex')).toBe('0'); + expect(lastFocusedElement).toBe(buttonB); + + // Pressing down should go to d. + ReactTestUtils.Simulate.keyDown(focusZone, { which: KeyCodes.down }); + expect(lastFocusedElement).toBe(buttonD); + }); + it('can use arrows bidirectionally with data-no-vertical-wrap', () => { const component = ReactTestUtils.renderIntoDocument(
diff --git a/packages/office-ui-fabric-react/src/components/FocusZone/FocusZone.tsx b/packages/office-ui-fabric-react/src/components/FocusZone/FocusZone.tsx index 2d3e703af8d23c..06b46f229cd708 100644 --- a/packages/office-ui-fabric-react/src/components/FocusZone/FocusZone.tsx +++ b/packages/office-ui-fabric-react/src/components/FocusZone/FocusZone.tsx @@ -308,11 +308,19 @@ export class FocusZone extends React.Component implements I } } + const initialElementFocused = !this._activeElement; + + // If the new active element is a child of this zone and received focus, + // update alignment an immediate descendant if (newActiveElement && newActiveElement !== this._activeElement) { + if (isImmediateDescendant || initialElementFocused) { + this._setFocusAlignment(newActiveElement, initialElementFocused); + } + this._activeElement = newActiveElement; - if (isImmediateDescendant) { - this._setFocusAlignment(this._activeElement); + if (initialElementFocused) { + this._updateTabIndexes(); } } diff --git a/packages/office-ui-fabric-react/src/components/FocusZone/examples/FocusZone.Photos.Example.tsx b/packages/office-ui-fabric-react/src/components/FocusZone/examples/FocusZone.Photos.Example.tsx index a210307742ddd0..fc61de1f8180bf 100644 --- a/packages/office-ui-fabric-react/src/components/FocusZone/examples/FocusZone.Photos.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/FocusZone/examples/FocusZone.Photos.Example.tsx @@ -11,7 +11,6 @@ export interface IPhoto { url: string; width: number; height: number; - onClick: (ev: React.MouseEvent) => void; } export class FocusZonePhotosExample extends React.Component<{}, { items: IPhoto[] }> { @@ -27,7 +26,6 @@ export class FocusZonePhotosExample extends React.Component<{}, { items: IPhoto[ return ( -

Note: clicking on the items below will remove them, illustrating how FocusZone will retain focus even when items are removed.

{items.map((item: IPhoto, index) => (
  • @@ -63,20 +60,7 @@ export class FocusZonePhotosExample extends React.Component<{}, { items: IPhoto[ id, url: `http://placehold.it/${randomWidth}x100`, width: randomWidth, - height: 100, - onClick: (ev: React.MouseEvent) => { - const items: IPhoto[] = this.state.items.filter((item: IPhoto) => item.id !== id); - - this.setState({ items }); - - // If we have run out of items, repopulate in a couple seconds. - if (items.length === 0) { - setTimeout(() => this.setState({ items: this._getInitialItems() }), 2000); - } - - ev.preventDefault(); - ev.stopPropagation(); - } + height: 100 }; } } diff --git a/packages/office-ui-fabric-react/src/components/__snapshots__/FocusZone.Photos.Example.tsx.shot b/packages/office-ui-fabric-react/src/components/__snapshots__/FocusZone.Photos.Example.tsx.shot index 0af554c1597579..9acebf80fa5566 100644 --- a/packages/office-ui-fabric-react/src/components/__snapshots__/FocusZone.Photos.Example.tsx.shot +++ b/packages/office-ui-fabric-react/src/components/__snapshots__/FocusZone.Photos.Example.tsx.shot @@ -9,16 +9,12 @@ exports[`Component Examples renders FocusZone.Photos.Example.tsx correctly 1`] = onMouseDownCapture={[Function]} role="presentation" > -

    - Note: clicking on the items below will remove them, illustrating how FocusZone will retain focus even when items are removed. -