-
Notifications
You must be signed in to change notification settings - Fork 55
feat(Accessibility): Improve accessibility behaviors, make them reusable #247
Conversation
Codecov Report
@@ Coverage Diff @@
## master #247 +/- ##
=======================================
Coverage 89.54% 89.54%
=======================================
Files 62 62
Lines 1176 1176
Branches 152 175 +23
=======================================
Hits 1053 1053
Misses 121 121
Partials 2 2
Continue to review full report at Codecov.
|
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.
Nice cleanup. Looks good.
src/lib/accessibility/interfaces.ts
Outdated
@@ -178,4 +178,4 @@ export type ActionsKeyHandler = { | |||
export type KeyboardHandler = (event: KeyboardEvent) => void | |||
export type EventHandler = (event: Event) => void | |||
|
|||
export type Accessibility = IAccessibilityDefinition | ((props: any) => IAccessibilityDefinition) | |||
export type Accessibility = ((props: any) => 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.
'Accessibility' is a very generic word, should we consider to change it to something more meaningful
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, quite generic, any ideas of a name?
Currently, we already have IAccessibilityDefinition
, IAccessibilityBehavior
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.
what do you think about IAcessibilityProvider?
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.
it's a type, so it will be AccessibilityProvider. Can be like that, we need more opinions and ideas :) @jurokapsiar what do you think on naming?
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 personally think Accessibility is ok, if there's concern, than it should probably be AccessibilityBehavior
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.
yeah, for me both are ok.
But guys, let's better merge this PR if the changes are ok for you.
And decide on naming other time, maybe we'll get inspired and find the perfect name :)
src/lib/accessibility/interfaces.ts
Outdated
@@ -178,4 +178,4 @@ export type ActionsKeyHandler = { | |||
export type KeyboardHandler = (event: KeyboardEvent) => void | |||
export type EventHandler = (event: Event) => void | |||
|
|||
export type Accessibility = IAccessibilityDefinition | ((props: any) => IAccessibilityDefinition) | |||
export type Accessibility = ((props: any) => 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.
nit: can be replaced with:
export type Accessibility = (props: any) => 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.
More important, any specific reason why we don't support just object definition, but only functions now? What is the problem of having both?
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.
We could have both object and functions, but as described above, it started to bring complexities which are more seen in the future, here are a couple of reasons:
- we always have to check whether we get function or object (as we aim to make accessibility framework agnostic, it can also bring some stress for the users, as they need to implement custom behavior derived from
Accessibility
type). - If we want to reuse some parts of other behavior, it would be quite unsafely or even impossible to make it nicely
- Already seen a not good way of implementation in
ListItemBehavior
, where we have to use a double function to pass props - And since most of the behaviors (for now like 80-90%) needs props to correctly render aria*-attributes, I don't see a reason to support such ambiguous structure for the rest of 10%
const ListItemBehavior: (props: any) => IAccessibilityDefinition = (props: any) => | ||
props.selection ? SelectableListItemBehavior(props) : BasicListItemBehavior | ||
const ListItemBehavior: Accessibility = (props: any) => | ||
props.selection ? SelectableListItemBehavior(props) : BasicListItemBehavior(props) |
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.
To me it feels kind a awkward that all accessibility behaviors are defined as functions but they are starting with capital letter.
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.
From point of view that functions should be lowercase - yes agree with that.
But for me, it seems better as it is now, and I can't explain why :)
If anyone also thinks about changing to lowercase, of course, we can consider it.
)(props) | ||
const accessibility: IAccessibilityDefinition = (customAccessibility || | ||
defaultAccessibility || | ||
DefaultBehavior)(props) | ||
|
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.
The callable util allow us exactly to have objects, or functions that can be invoked for the same Type. See the comment above.
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 - but even from this perspective, now we handle if we get correct type (what is not good) from outside. In my opinion, this should be handled in accessibility functionality. We should treat Accessibility as a separate lib, we just give information to it, and it does everything for us. Anyway, I answered above what were the reasons for such changes :)
Nice changes, @sophieH29 . Left some comments for consideration, and please fix the linting errors. After reasoning for/resolving the comments, should be good for merge. |
# Conflicts: # src/lib/accessibility/Behaviors/Tab/TabListBehavior.ts # src/lib/accessibility/Behaviors/Toolbar/ToolbarBehavior.ts
role: props.as === 'button' ? undefined : 'button', | ||
'aria-disabled': !!props['disabled'], | ||
}, | ||
export const ButtonAttributes = (props: any): AccessibilityAttributes => ({ |
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.
How does this help with reusability? If ToggleButtonBehavior
is built on top of ButtonBehavior
, it can call ButtonBehavior
directly:
const parent = ButtonBehavior(props)
return {
attributes: {
root: {
...parent.attributes.root,
'aria-pressed': !!props['active'],
'aria-disabled': !!props['disabled'],
},
},
}
Extracting to ButtonAttributes
can be understood as only behaviors with extracted attributes can be 'extended' which (1) is not true and (2) limits extending behaviors for library users.
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.
@miroslavstastny agree with your point that it is possible to do that as in your example, although I wouldn't call it "parent". It is like a composition - you can build new behavior with different parts. + in your approach the whole behavior is initialized and we use from it just part, so it can have a bigger performance impact.
Let me know what you think.
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.
Understand, but do we need it now or is it just a premature optimisation? Especially considering that currently the only refactored behavior is ButtonBehavior
which has just attributes
and nothing else.
I think that your approach is valid when it is required but I would not define it as the only/primary reusability pattern.
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.
The main change in this PR - is to give the behavior a solid structure, so now it can be only a function. With this, we remove ambiguity and make it simpler.
The change in ButtonBehavior is just a bonus.
I think that your approach is valid when it is required but I would not define it as the only/primary reusability pattern.
- agree with that, this approach of reusing parts is not a primary pattern
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.
Don't take me wrong, I like the change to functions.
I would just prefer not to have ButtonAttributes
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.
Got your point @miroslavstastny, let's agree then, I will remove ButtonAttributes
as this is not really needed right now.
@@ -5,7 +5,7 @@ import { Accessibility, FocusZoneMode } from '../../interfaces' | |||
* Adds role 'presentation' to 'root' component's part. | |||
* Wraps component in FocusZone allowing arrow key navigation through the children of the component. | |||
*/ | |||
const ButtonGroupBehavior: Accessibility = { | |||
const ButtonGroupBehavior: Accessibility = (props: any) => ({ |
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.
Behaviors are functions, not objects. Why their names start with capital letters?
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.
From point of view that functions should be lowercase - yes agree with that.
I left as is just from that point how it looks
static defaultProps = {
accessibility: MenuBehavior as Accessibility,
}
vs
static defaultProps = {
accessibility: menuBehavior as Accessibility,
}
Marija has same point on that, so then I will change to lowercase
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.
Speaking of as
, according to @smykhailov it is no longer necessary after @kuzhelov fixed generated paths in .d.ts.
Another thing is that defaultProps
should be typed to components' prop interface :-)
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.
Another thing is that defaultProps should be typed to components' prop interface :-)
yeah, I know that
Ok, just to summarize, I'll make behaviors lower case.
I think defaultProps typing should be fixed as separate PR
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.
changelog and go!
Currently, accessibility behaviors bring few constraints on how they are implemented and used.
Main points:
ToggleButtonBehaviour
attributes based onButtonBehavior
, without copy-pasting same codeconst SelectableListItemBehavior: (props: any) => IAccessibilityDefinition = (props: any) => ({
We define it with double function to pass correct props from
BasicListBehavior
Solution is to change accessibility behavior definition to one solid structure
Reusability
Currently I made
ButtonBehavior
attributes reusable, by exporting it's attributes and using it inToggleButtonBehavior
asOther behaviors can be changed like that in case we'll see any other meaningful opportunity to reuse behavior's parts (attributes, keyActions, etc).