Skip to content

Conversation

@jspurlin
Copy link
Contributor

Pull request checklist

  • Include a change request file using $ npm run change

Description of changes

For FocusZones that contain multiple navigation models (especially when one is a super set of the other) the resulting navigation can muddy. For example, if a ContextualMenu contains a section with 2d layout and another section with 1d layout, the FocusZone is bidirectional to allow for the 2d navigation but this makes left/right naviagate the 1d section of the menu (and additionally breaks the submenu arrow collapse behavior). The change adds a check for data-no-horizontal-wrap and data-no-vertical-wrap on the element the FocusZone is acting on (it check the element itself as well and all ancestors until it finds a match or hits the body). Additionally this adds a checkForNoWrap prop to FocusZone and ContextualMenu so that we won't do the searching for the data attributes unless the creator specifies that we should be looking for it.

This is a before behavior: the vertical wrapping when the focus gets to the top of the 2d section (e.g. focus moves to the left instead of wrapping); also note that the navigation in the 1d layout is moving when left/right are pressed (and pressing left in the 1d layout does not collapse the submenu)
contextualmenubefore

This is the after behavior: the vertical wrapping in the 2d layout, the horizontal wrapping in the 1d layout, and the ability for the submenu to collapse with the arrow keys are all working as expected now
contextualmenuwithnowrap

Focus areas to test

Verified that existing behavior has not changed at all, and the new behavior is scoped to only when the user explicitly wants that behavior.

@joschect
Copy link
Contributor

I want to try to avoid adding a bunch of new properties to contextualmenu's overall. It seems like having a catch all focuszoneprops would be a good way to help cut down on the clutter and provide some of this functionality.

I think the changes to the focus zone could be useful, but for now this same problem could be solved by custom rendering an item which contains a focus zone and then controlling which keypresses trigger an entry into the subfocuszone. I don't know if that's the right way to accomplish things either but it would allow users to do this without having a more complicated focuszone.

@jspurlin
Copy link
Contributor Author

Yeah, I thought about adding a focusZoneProps which should include deprecating arrowDirection, not opposed to making this change

I attempted the nested focus zone approach but it was overly complicated and didn't solve all of the issues I was running in to.

I had chatted with @dzearing about approaches to solve this issue we landed on the approach I implemented. @joschect let's chat offline if you have an idea on how get the desired end result: where you can have an vertical outer focus zone with a bidirectional focus zone inside (for the 2d content) where focus flows between them as if they were one focus zone (especially when entering the bidirectional focus zone from top and bottom), the keyboarding doesn't move left when you press up at the top of a 2d set of controls (when you are also not the control in the left-most column. In the before gif (in the description) this happens starting when focus is on the airplane), and collapses submenus when focus is in the 1d portion and the appropriate left/right arrow key is presssed (which you should get for free with a vertical focus zone)

"changes": [
{
"packageName": "@uifabric/utilities",
"comment": "Focus/DOM: add the ability to find if an element (or any ancestor) contians a given attribute; to the dom utility ; add a shouldrWapFocus function to the focus utility ",
Copy link
Member

Choose a reason for hiding this comment

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

These go into the release notes, and you have some extra spaces and typos

getDistanceFromCenter: (activeRect: ClientRect, targetRect: ClientRect) => number,
ev?: Event): boolean {
ev?: Event,
shouldWrap: boolean = true): boolean {
Copy link
Member

Choose a reason for hiding this comment

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

it is weird to default this to true. it should default to false.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the name is misleading, I'll update it to be useDefaultWrap so that it's more clear that when this is true it will do the default wrapping behavior

distance = activeRect.right - targetRect.right;
} else {
if (!shouldWrap) {
distance = -2;
Copy link
Member

Choose a reason for hiding this comment

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

I don't quite understand the -2

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's just one less than -1 :) I needed to use a more negative number (for the distance from center) to keep the loop going in. I pulled out a const for both the upper value (that already existed) and the lower value that I added. The large value picks the element as a potential element, while the negative number is for not picking that element

* @param attribute - the attribute to search for
* @returns the value of the first instance found
*/
export function elementContainsAttribute(element: HTMLElement, attribute: string): string | undefined {
Copy link
Member

Choose a reason for hiding this comment

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

I feel like I've seen this code repeated elsewhere.

Could we just have one method to find a node which matches some criteria?

Maybe findElementRecursive(cb: (el) => boolean): HTMLElement or something:

// hmm better name?
findElementRecursive(el => el.hasAttribute(attr));

Then you could reuse the "go up the parents" logic elsewhere.

getElementAttributeRecursive(el, attr) {
const el = findElementRecursive(el => el.hasAttribute(attr));

return el && el.getAttribute(attr);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I'll look into this

* @returns the value of the first instance found
*/
export function elementContainsAttribute(element: HTMLElement, attribute: string): string | null {
let elementMatch = findElementRecursive(element, element => element.hasAttribute(attribute));
Copy link
Member

Choose a reason for hiding this comment

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

ERROR: 228:52 typedef expected arrow-parameter: 'element' to have a typedef
ERROR: 228:52 no-shadowed-variable Shadowed name: 'element'

Copy link
Member

Choose a reason for hiding this comment

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

maybe I can inline fix it for you

}

/**
* Finds the first parent element where the matchFunctionReturns true
Copy link
Member

Choose a reason for hiding this comment

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

typo

* @returns the value of the first instance found
*/
export function elementContainsAttribute(element: HTMLElement, attribute: string): string | null {
let elementMatch = findElementRecursive(element, (testElement: HTMLElement) => testElement.hasAttribute(attribute));
Copy link
Member

Choose a reason for hiding this comment

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

i fixed the lint errors here. Also fixed the comment typo above.

One other note: on 218, I changed element.parent to getParent(element). This allows for virtual parent chains to be respected.

@jspurlin
Copy link
Contributor Author

@dzearing not sure why there's some screener changes, it's showing some slight changes for date pickers but I'm not changing any styling....

@dzearing dzearing closed this Feb 22, 2018
@dzearing dzearing reopened this Feb 22, 2018
@dzearing
Copy link
Member

Large_Negative_DISTANCE_FROM_CENTER.... hmmmmmmmm

@jspurlin
Copy link
Contributor Author

yep, fixed the casing and am trying to push the update

@jspurlin jspurlin merged commit b055014 into microsoft:master Feb 22, 2018
@microsoft microsoft locked as resolved and limited conversation to collaborators Aug 31, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants