-
Notifications
You must be signed in to change notification settings - Fork 55
feat(FocusZone): Implement FocusZone into renderComponent #116
Conversation
Codecov Report
@@ Coverage Diff @@
## master #116 +/- ##
===========================================
+ Coverage 67.86% 89.04% +21.18%
===========================================
Files 101 47 -54
Lines 1366 776 -590
Branches 270 101 -169
===========================================
- Hits 927 691 -236
+ Misses 437 83 -354
Partials 2 2
Continue to review full report at Codecov.
|
mode: FocusZoneMode.Wrap, | ||
props: { | ||
onActiveElementChanged: (element, ev) => { | ||
console.error('MENU BEHAVIOR', 'on active element changed', 'element', element, 'ev', ev) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to have this? We'll get many errors into console then :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, we should decide if this PR should modify MenuBehavior.ts
or not. If yes, then we probably do not need the props
at all? @jurokapsiar
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for now you can set in props isCircularNavigation: true
, as it's a requirement by ARIA documentation
Also, It might be in separate PR, but would be good to have VerticalMenuBehavior as we have for VerticalMenuItemBehavior, so in props can be set directions:
props: {
direction: FocusZoneDirection.horizontal, // vertical for Vertical behavior
isCircularNavigation: true,
},
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree with @sophieH29, we should include props, but not onActiveElementChanged
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
and horizontal/vertical menu should be in a separate PR. for now let's just include isCircularNavigation
@@ -8,7 +8,7 @@ const MenuItemBehavior: Accessibility = (props: any) => ({ | |||
anchor: { | |||
role: 'menuitem', | |||
'aria-expanded': props['submenuOpened'], | |||
tabIndex: '0', | |||
'data-is-focusable': true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am having a concern of using hardcode attribute which is directly related to FocusZone. So if it changes in Focus, we'll have to update all the behaviors.
What do you think if we move data-is-focusable
to interfaces.ts?
export const IS_FOCUSABLE_ATTRIBUTE = 'data-is-focusable'
and use it in behaviors
...
[IS_FOCUSABLE_ATTRIBUTE]: 'true'
...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree
…at/acc-focuszone-in-rendercomponent
.childAt(0) // <div> inside FocusZone wrap | ||
.childAt(0) // the actual component | ||
} | ||
return component |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have idea how will the Custom FocusZone be handled? Is it going to be completely different implementation for each custom zone, where in some cases there will be wrappers and in some other won't. I am a bit scared about how we would be able to provide the rendered component itself in those cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we test here the mode wrap in this case as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the idea of FocusZoneMode is still a little bit uncertain. The custom FocusZoneMode is that the consumer of Stardust would provide his own functions, meaning that it will actually never affect these unit tests. I think @jurokapsiar could provide more details? Should we modify this test to check FocusZoneMode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes - custom means that the focus zone functionality is handled outside of Stardust. We have test (handlesAccessibility for menu-test) that checks if the component is correctly wrapped in the focus zone. The isConformant just skips FocusZone if it finds it. Currently we do not have tests that test only the behaviors - we just have tests that check, if the behaviors correctly modify particular components. It would be useful to 3 tests that just check if renderComponent uses FocusZone correctly - one for wrap, one for custom and one without FocusZone. Is this what you meant @mnajdova?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that the custom mode is basically equivalent to disabling FocusZone. Is that right, @jurokapsiar? In that case, maybe we can completely eliminate the FocusZoneMode for now. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jurokapsiar you are right that's what I meant, explicitly handing having mode wrap, custom or no focus zone, but I was uncertain what is the expected result of having custom focus zone. If it all sums up to having this condition, then we are good to stay with this condition as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, let's leave it as it is for now. :)
.prop('className') | ||
return classes | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for refactoring this!
const target = partSelector | ||
? renderedComponent.render().find(partSelector) | ||
: renderedComponent.render() | ||
|
||
return target.first().prop(propName) | ||
let node = target.first() | ||
if (isWrappedInFocusZone) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add here check for the node, as using partSelector might return nothing if it doesn't exists in the component. If the intention is for the test to fall in that case, I would rather handle that and throw explicit exception explaining what was the cause for the test to fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! So I investigated and actually, render()
should return Cheerio objects and Cheerio has a chaining API which always returns another Cheerio object, meaning it can never be undefined and you can call functions even on empty Cheerio wrappers.
So what happens if no element is matched is that the whole getProp
function would just return undefined
from the last .prop(...)
call, which is exactly how it worked before my PR.
I guess it's ok to keep it as it is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, should be ok then.
src/lib/renderComponent.tsx
Outdated
@@ -93,7 +115,7 @@ const renderComponent = <P extends {}>( | |||
const classes: IComponentPartClasses = getClasses(renderer, mergedStyles, styleParam) | |||
classes.root = cx(className, classes.root, props.className) | |||
|
|||
const accessibility = getAccessibility(props, state) | |||
const accessibility = getAccessibility(props, state) as IAccessibilityDefinition |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would modify getAccessibility()
to return IAccessibilityDefinition
and remove the cast here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree, done! :)
…b.com/stardust-ui/react into feat/acc-focuszone-in-rendercomponent
…in-rendercomponent
Codecov Report
@@ Coverage Diff @@
## master #116 +/- ##
=======================================
Coverage 91.53% 91.53%
=======================================
Files 61 61
Lines 1028 1028
Branches 136 136
=======================================
Hits 941 941
Misses 83 83
Partials 4 4 Continue to review full report at Codecov.
|
…in-rendercomponent # Conflicts: # src/lib/accessibility/Behaviors/Menu/MenuItemBehavior.ts # src/lib/accessibility/interfaces.ts # src/lib/renderComponent.tsx # test/specs/components/Menu/Menu-test.tsx
…in-rendercomponent
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RTL support and more Behaviors will be added in separate PRs.
Implement FocusZone into renderComponent
Hi, this PR does the following:
@uifabric/utilities
intopackage.json
,FocusZone
intolib/accessibility
,focusZone
key intoIAccessibilityDefinition
,renderComponent.ts
to wrap a component in<FocusZone ...>...</FocusZone>
if it's set in its accessibility properties,MenuBehavior.ts
to enable keyboard navigation in menu and show howFocusZone
works,and modifies unit tests the following:
handlesAccessibility.tsx
: adds unifiedTestBehavior
and fixesFocusZone
wrapping,isConformant.tsx
to fixFocusZone
wrapping,FocusZone
based on FabricFocusZone.test.tsx
.The user of the FocusZone has to set up the focus zone in a corresponding
xxxBehavior.ts
file like so:The
FocusZoneMode.Wrap
means that the component will be wrapped by the FocusZone, creating an additional<div>
around it and handling the focus. We might need to add a different mode in the future likeFocusZoneMode.Embed
but we are not sure yet. A consumer of a component may overload the accessibility by usingFocusZoneMode.Custom
.