-
Notifications
You must be signed in to change notification settings - Fork 841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor(selectable): avoid prop conflicts #4291
Conversation
Since this is a community submitted pull request, a Jenkins build has not been kicked off automatically. Can an Elastic organization member please verify the contents of this patch and then kick off a build manually? |
) => { | ||
if (props && props['aria-label']) { | ||
return { 'aria-label': props['aria-label'] }; | ||
const getAriaProps = ( |
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 renamed this function, because it's now setting multiple props, or returning undefined if no suitable aria props can be set.
type AriaLabelProps = { | ||
'aria-label': string; | ||
'aria-describedby': undefined; | ||
}; |
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.
When using aria-label
, always override aria-describedby
.
type AriaDescribedbyProps = { | ||
'aria-label': undefined; | ||
'aria-describedby': string; | ||
}; |
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.
As above, do not set aria-label
, if aria-described
is used.
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 you changed this logic a little (which was, granted, somewhat convoluted)... Either way, the intended effect is that both aria-label
and aria-describedby
is allowed. We just need to move where the attribute is being set.
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.
@myasonik I'm still not sure where the logic has changed (but I agree, it's somewhat convoluted, and I could certainly be missing something).
I'm happy to make some more changes to accommodate the expected behaviour. Based on your last comment, I think the required logic is:
aria-label
- Uses
searchProps['aria-label']
orlistProps['aria-label']
if set - Else, uses
aria-label
passed toEuiSelectable
- Uses a fallback of
placeholderName
- Uses
aria-describedby
- Uses
searchProps['aria-describedby']
orlistProps['aria-describedby']
if set - Else, uses
aria-describedby
passed toEuiSelectable
- In either of the above cases,
messageContentId
is added to the end. - No fallback is used.
- Uses
Does this look correct? Thanks!
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.
Sorry about the long delay on getting back to you @smhutch - I was off for US Thanksgiving for a while but am back at it now!
I think what you outlined is generally correct but I'm going to restate it in a little more depth just to make sure we're on the same page.
First, let's define where we're going to be setting these props. There are 2 possible spots:
1. the input field (which has these values passed along through the ...rest
param)
2. the list box (which sets these values using some ref hackiness)
The overarching rule is that each of these can be controlled individually with searchProps
or listProps
respectively but also need to respect broader values set on EuiSelectable
itself.
So, for the input field:
aria-label
- Use
searchProps['aria-label']
- Use
aria-label
passed intoEuiSelectable
- Use
placeholderName
- Use
aria-describedby
- Use
searchProps['aria-describedby']
andmessageContentId
as a space-separated string - Use
aria-label
passed intoEuiSelectable
andmessageContentId
as a space-separated string - Use
messageContentId
- Use
For the list box, it is the same as above but replace searchProps
with listProps
.
Writing this up, I also realized we have a gap by not covering aria-labelledby
in conjunction with aria-label
but I think we ignore that for now and tackle it in a future PR. (If you do want to tackle it, let me know and I can write up some guidelines but it'll double the length of the aria-label
logic...)
type AriaProps = Partial<{ | ||
'aria-label': string; | ||
'aria-describedby': string; | ||
}>; |
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.
Previously this was:
| Partial<EuiSelectableSearchProps<T>>
| EuiSelectableOptionsListPropsWithDefaults
I changed to only require the keys that are needed within this function. Any object that might have these set may be used with this function.
const getAriaProps = ( | ||
props: AriaProps, | ||
describedbyId?: string | ||
): AriaPropsReturn => { |
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.
Added an explicit return type. Not 100% needed, but I think it's helpful.
: { 'aria-label': placeholderName })} | ||
{...cleanedSearchProps} | ||
{...searchProps} | ||
onChange={this.onSearchChange} |
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.
Instead of spreading ...cleanedSeachProps
, I took the approach of not spreading at the end. Any values that come after ...searchProps
will override those which are spread.
In this case, onChange
and ...ariaProps
will override whatever is passed to ...searchProps
I have a feeling that there are more potential issues lurking here (i.e. should searchProps.placeholder
override placeholder
?)
singleSelection, | ||
isLoading, | ||
listProps, | ||
listProps = {}, |
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.
Setting this to an empty object by default, avoids the possibility of it being undefined
. Which simplifies some other code (no need to check that it's not undefined
, before accessing properties). 😄
@@ -333,9 +333,6 @@ export class EuiSelectable<T = {}> extends Component< | |||
} | |||
} | |||
); | |||
if (this.props.searchProps && this.props.searchProps.onSearch) { | |||
this.props.searchProps.onSearch(searchValue); | |||
} |
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.
Here we were calling searchProps.onSearch
, from a handler which was passed down as onChange
. This code is still called, but is now handled within selectable_search.tsx
|
||
if (this.props.onSearch) { | ||
this.props.onSearch(value); | ||
} |
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.
Allows consumers to hook into onSearch
calls.
incremental | ||
defaultValue={defaultValue} | ||
fullWidth | ||
autoComplete="off" | ||
aria-haspopup="listbox" | ||
{...ariaPropsIfListIsPresent} | ||
{...rest} | ||
onSearch={this.onSearchChange} |
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.
onSearch
is moved to here, to prevent ...rest
from overriding it.
Just revisiting this. I won't have time to update this PR for at least a few more weeks, but I do intend to come back and make the required changes. |
👍 Thanks for the update, @smhutch |
👋 Hey there. This PR hasn't had any activity for 90 days. We'll automatically close it if that trend continues for another week. If you feel this issue is still valid and needs attention please let us know with a comment. |
Hey @thompsongl, I started a new job, and don't have time to resolve this any more. Closing for now. |
Summary
I'm planning to open another PR to address #4262. While looking starting working on that, I noticed some code patterns that I see as problematic. The issue I see is that we have cases of props conflicting with each other (in this case top-level props conflicting with
seachProps
). The current code mostly fixes these issues by creating a...cleanedSearchProps
, which is all ofsearchProps
minus some de-structured values. While this approach works, it's quite unstable. Another engineer could easily confuse these unused variables as "useless", and remove them. Similarly, we need to jump through some hoops to keep TypeScript happy about this approach (in this type casting).The approach I have taken instead is to not spread
...searchProps
over all others. Instead, I have continued to spread these, but added a couple of explicit overrides afterwards.As this change doesn't explicitly fix an open issue, and is to some extent an opinionated change. I'm interested in hearing some thoughts on my approach from the Elastic team. Thanks!
Related to
#4132
#4262
Checklist