Skip to content

FocusTrapZone: Refactor trapping behavior#8216

Merged
JasonGore merged 7 commits intomicrosoft:masterfrom
JasonGore:jg/ftz-refactor-trapping
Mar 12, 2019
Merged

FocusTrapZone: Refactor trapping behavior#8216
JasonGore merged 7 commits intomicrosoft:masterfrom
JasonGore:jg/ftz-refactor-trapping

Conversation

@JasonGore
Copy link
Member

@JasonGore JasonGore commented Mar 6, 2019

Pull request checklist

Description of changes

The purpose of this PR is to refactor FocusTrapZone (FTZ) focus trapping behavior to resolve outstanding and recurring issues as described in #8055 and fixes multiple open issues.

The refactor trapping behavior has been fixed from a key-handler based approach to a focus boundary approach. The fundamental difference is that now FTZ does not care how individual elements handle navigation within its boundaries. Now FTZ only cares when navigation attempts to leave FTZ boundaries. This fixes multiple issues regarding navigation issues with FTZ content. (There are some exceptions to this approach regarding nested FTZs and FocusZones.)

While refactoring trapping behavior, I also encountered the following issues / areas of improvement, which this PR also addresses:

  • Focus utilities returning non-tabbable elements as tabbable.
  • FTZ and focus utility unit test coverage.
  • Broken FTZ examples.
  • Broken FTZ behavior with 0 tabbable elements.
  • No FTZ examples for 0 tabbable elements and embedded FocusZones.

This PR fixes multiple open FTZ navigation issues. I've also made sure recently fixed issues have not regressed. I've set up some CodePens running against an ngrok host (forgive me if these aren't working, I'll try to keep the server running. This has also made me realize it'd be nice to have PR bundles hosted for CodePens in general... #8217)

EDIT: It's really hard to keep these CodePens up and running because ngrok keeps timing out at some point. If you want to review them just let me know and I'll make sure they're running for a bit.

Issue # Example Running Against this PR State
#4191 https://codepen.io/jasongore/pen/rPEamG Still works.
#7679 https://codepen.io/jasongore/pen/gqNEpG Still works.
#4649 https://codepen.io/jasongore/pen/qgzdbG Fixed!
#5160 https://codepen.io/jasongore/pen/pGXvEE Fixed! (again)
#6526 https://codepen.io/jasongore/pen/omrVbL Fixed!
#7891 https://codepen.io/jasongore/pen/bzPZyJ Still works.
#7992, #8011 https://codepen.io/jasongore/pen/PVrgEr Still works.
#8136 DatePicker page Fixed!

#7839 still seems to be an issue but I think this is PR is a really good step and should be rereviewed in the context of #8055 later. (i.e. I need an FTZ break.)

Focus areas to test

Unit test coverage increased for FTZ and focus utilities.

Microsoft Reviewers: Open in CodeFlow


const bumperStyle: React.CSSProperties = {
pointerEvents: 'none'
};
Copy link
Member Author

Choose a reason for hiding this comment

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

@dzearing I could use your eyes on this on potential side effects / issues with this style. I tried various other methods:

display: none

This breaks onFocus callbacks

display: contents

This breaks onFocus callbacks

position: absolute, left: -9999px, top: -9999px

This was causing weird browser scrolling issues when changing focus

@size-auditor
Copy link

size-auditor bot commented Mar 6, 2019

Bundle test Size (minified) Diff from master
Dialog 193.49 kB ExceedsBaseline     763 bytes
DatePicker 204.043 kB ExceedsBaseline     763 bytes
FocusTrapZone 33.349 kB ExceedsBaseline     763 bytes
Panel 191.839 kB ExceedsBaseline     763 bytes
HoverCard 89.794 kB ExceedsBaseline     763 bytes
Dropdown 220.596 kB ExceedsBaseline     763 bytes
Callout 80.861 kB ExceedsBaseline     763 bytes
Modal 68.916 kB ExceedsBaseline     763 bytes
Coachmark 88.915 kB ExceedsBaseline     763 bytes
Persona 112.766 kB ExceedsBaseline     42 bytes
ContextualMenu 157.154 kB ExceedsBaseline     42 bytes
OverflowSet 60.554 kB ExceedsBaseline     42 bytes
Nav 183.635 kB ExceedsBaseline     42 bytes
MessageBar 183.957 kB ExceedsBaseline     42 bytes
MarqueeSelection 62.767 kB ExceedsBaseline     42 bytes
Utilities 69.449 kB ExceedsBaseline     42 bytes
DetailsList 212.754 kB ExceedsBaseline     42 bytes
Tooltip 81.729 kB ExceedsBaseline     42 bytes
CommandBar 195.458 kB ExceedsBaseline     42 bytes
KeytipLayer 96.17 kB ExceedsBaseline     42 bytes
Keytip 79.969 kB ExceedsBaseline     42 bytes
DocumentCard 207.401 kB ExceedsBaseline     42 bytes
ExtendedPicker 90.62 kB ExceedsBaseline     42 bytes
GroupedList 125.127 kB ExceedsBaseline     42 bytes
Grid 177.406 kB ExceedsBaseline     42 bytes
FocusZone 32.886 kB ExceedsBaseline     42 bytes
PersonaCoin 112.788 kB ExceedsBaseline     42 bytes
Pickers 270.978 kB ExceedsBaseline     42 bytes
Facepile 204.546 kB ExceedsBaseline     42 bytes
Selection 47.739 kB ExceedsBaseline     42 bytes
Breadcrumb 193.884 kB ExceedsBaseline     42 bytes
Button 187.297 kB ExceedsBaseline     42 bytes
TeachingBubble 189.113 kB ExceedsBaseline     42 bytes
SwatchColorPicker 192.21 kB ExceedsBaseline     42 bytes
Calendar 141.06 kB ExceedsBaseline     42 bytes
SpinButton 188.067 kB ExceedsBaseline     42 bytes
Pivot 182.421 kB ExceedsBaseline     42 bytes
ShimmeredDetailsList 224.765 kB ExceedsBaseline     42 bytes
FloatingPicker 231.567 kB ExceedsBaseline     42 bytes
SelectedItemsList 220.773 kB ExceedsBaseline     42 bytes
SearchBox 180.936 kB ExceedsBaseline     42 bytes
Rating 75.126 kB ExceedsBaseline     42 bytes
ComboBox 232.981 kB ExceedsBaseline     42 bytes
PositioningContainer 68.636 kB ExceedsBaseline     42 bytes
Popup 30.789 kB ExceedsBaseline     42 bytes

ExceedsTolerance  Exceeds Tolerance     ExceedsBaseline  Exceeds Baseline     BelowBaseline  Below Baseline     1 kB = 1000 bytes

@@ -16,6 +15,10 @@ export class FocusTrapZone extends BaseComponent<IFocusTrapZoneProps, {}> implem
private static _focusStack: FocusTrapZone[] = [];
Copy link
Member Author

@JasonGore JasonGore Mar 6, 2019

Choose a reason for hiding this comment

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

@dagoff I saw this was added in #1111 which said it was added to prevent an infinite loop and I'm wondering if it's still needed. Was the infinite looping related to key handling? I did some brief experimentation of removing it with this new trapping behavior and I couldn't trigger any infinite loops.

Copy link
Member

Choose a reason for hiding this comment

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

The original trapping behavior (before I added focusStack) was that a FocusTrapZone would listen to all focus events, then if a focus event fired, the FocusTrapZone would check to see if the new focus target was inside the Zone. If it wasn't, the FocusTrapZone would forcibly focus something inside of it self. This would fire off another focus event, but the target would be in the Zone, so it wouldn't cause a loop if there was only one Zone. This worked fine when only 1 FocusTrapZone was on the page. With 2 or more, it was easy to end up in a state where they would fight for focus forever.

focusStack was an attempt to fix this. In short, each FocusTrapZone would push itself onto the stack at mount time and only the Zone on top of the stack is allowed to try to steal focus (or clicks).

// If the default has been prevented, do not process keyboard events.
if (ev.isDefaultPrevented()) {
return;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

@jspurlin I see this was added in #4486, which basically allows consumers to block FTZ focus behavior. This PR will break this behavior, but I'm not sure how or why consumers would do this. What are valid use cases for this?

Copy link
Contributor

Choose a reason for hiding this comment

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

There might be cases when a consumer might want to do something special in specific cases. For example, if TAB normally navigates linearly across focusable elements, but in some case(s) the consumer wants to move focus to a different element.

Removing this will break Office Online

Copy link
Member Author

@JasonGore JasonGore Mar 7, 2019

Choose a reason for hiding this comment

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

FocusTrapZone does not modify or affect focus behavior within it. It does not control focus within its contents anymore, so there's no longer any FTZ behavior to "stop" here.

If consumers of FTZ want to modify focus behavior, they can still do that. Nothing in FTZ is preventing key or focus handlers from working.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, after talking offline, getting on the same page, and since the divProps are still getting applied to the div _onKeyboardHandler isn't needed and things should continue to work

Copy link
Contributor

@jspurlin jspurlin left a comment

Choose a reason for hiding this comment

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

Removing the return on keydown handler in FocusTrapZone if preventDefault is called on the event breaks behavior in Office Online

@JasonGore JasonGore requested a review from Vitalius1 as a code owner March 12, 2019 00:45
@JasonGore JasonGore merged commit 882f4b9 into microsoft:master Mar 12, 2019
aftab-hassan pushed a commit to aftab-hassan/office-ui-fabric-react that referenced this pull request Mar 12, 2019
* Refactor FocusTrapZone trapping behavior from key handler approach to focus boundary approach.

* Change files.

* Remove unnecessary file.

* Minor cleanup.

* Bundle size optimization.

* Prevent browsers from focusing on bumpers in cases where viewport does not contain them.
@msft-github-bot
Copy link
Contributor

🎉@uifabric/utilities@v6.35.0 has been released which incorporates this pull request.:tada:

Handy links:

@msft-github-bot
Copy link
Contributor

🎉office-ui-fabric-react@v6.155.0 has been released which incorporates this pull request.:tada:

Handy links:

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

7 participants