-
Notifications
You must be signed in to change notification settings - Fork 77
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
fix(list): include groups in filtering #10664
fix(list): include groups in filtering #10664
Conversation
This PR has been automatically marked as stale because it has not had recent activity. Please close your PR if it is no longer relevant. Thank you for your contributions. |
This PR has been automatically marked as stale because it has not had recent activity. Please close your PR if it is no longer relevant. Thank you for your contributions. |
This PR has been automatically marked as stale because it has not had recent activity. Please close your PR if it is no longer relevant. Thank you for your contributions. |
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.
Looking good @anveshmekala but have some feedback on implementation.
* @readonly | ||
* @deprecated Use `filteredResults` instead. | ||
*/ | ||
@property() filteredItems: ListElement[] = []; |
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.
@anveshmekala can we leave filteredItems? Do we need to have a property that includes groups?
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.
since it is a short notice, its better to deprecate first. To avoid confusing with naming, filteredResults
is added.
If we are including groups in filter, the user should be able to access the groups me thinks.
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.
Part of my reasoning for not including groups is that they aren't results. They can't be selected, they don't have a value, etc.
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 item-group is more of a presentational component, but using heading
as filterProp
can affect how filtering is done. And users should be able to lookup the API for any potential matches for filterText.
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 will make it easier if we don't include item-group
in filteredItems but filteredItems can't be empty if there is a match.
Did think of adding direct children items of the item-group as filteredItems instead of the item-group but that can be misleading and not consistent with how items are added to filteredItems
when item is nested inside a item without a group. Thoughts?
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.
After reviewing the issue again, I think the request was primarily visual and not related to filteredItems
. My earlier context was minimal, so apologies for any confusion. 😅
I agree on including them when they are valueless is a bit odd.
Could we proceed with the behavior while keeping filteredItems
as is?
@@ -160,7 +171,7 @@ export class List | |||
|
|||
@state() assistiveText: string; | |||
|
|||
@state() dataForFilter: ItemData = []; | |||
@state() dataForFilter: (ItemData | ItemGroupData)[] = []; |
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 including separate group data, can we just add the group data to each item within a group? It would just be the heading
property added to each item.
If we just added group heading to the itemData, then we wouldn't need the logic in filterGroupItems
Theres already existing logic to show a group if one or more items match and hide a group if not. The only part missing is the group data attached to each item.
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 above wouldn't work where only group heading is matched (nested group) but none of the items or ancestor group have a matching text.
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.
@anveshmekala it should work.
The current filtering only applies to items, but should also include groups and any matches should show all items under a matching group.
any matches should show all items under a matching group
According to the request, if a group matches, all items under that group should also match.
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.
Its working as expected! 🎉
{filteredItems.map((item) => ( | ||
<li>{item.label}</li> | ||
{filteredResults.map((item) => ( | ||
<li role={(item as ListItemGroup).heading ? "group" : "none"}> |
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.
For proper semantic structure, groups should be rendered as <ul>
or <ol>
and no roles would need to be set here. Setting the role to none
is taking away from the list role which is automatically applied to <li>
elements.
The structure should be like:
<ol>
<li>Group 1
<ol>
<li>item within group</li>
<li>item within group</li>
</ol>
</li>
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, the role
attribute wouldn't make a diff how AT handles the information. Idea was to provide the context about a possible group heading matched & semantic structure is not the right fit.
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.
My point was that if you set the role to none
on a list-item it takes away the default list-item role.
@@ -744,7 +761,7 @@ export class List | |||
const filteredItems = filterPredicate |
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 way this is currently structured, filterPredicate would only affect items and not groups.
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.
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 don't think it should, just wanted to point out the inconsistency with treating groups like items.
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.
If not, is the alternative metadata
+ filterProps
? While functional, it introduces boilerplate for a common filtering workflow using information already present in the list.
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.
Getting closer @anveshmekala! 👍
return this.listItems.map((item) => ({ | ||
label: item.label, | ||
description: item.description, | ||
metadata: item.metadata, | ||
heading: item.parentElement?.closest(listItemGroupSelector)?.heading || "", |
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.
Can we store the parentGroup elements on connectedCallback? That way its only ever queried in the DOM one time. In this scenario its queried every time getItemData()
is called.
The other part is that this is only getting the first parent group element. Shouldn't it get all ancestor group elements and map to their headings?
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 other part is that this is only getting the first parent group element. Shouldn't it get all ancestor group elements and map to their headings?
Good catch. Each item should store the heading of all ancestors group elements.
let heading = ""; | ||
this.listItemGroups.forEach((group) => { | ||
if (group.contains(item)) { | ||
heading += group.heading; |
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 also store heading of ancestor as Array object.
Leaning towards string concatenation for performance gains with filter
method.
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 I was thinking array because string concat would match two words from different headings combined. Can we change to use an array here?
let heading = ""; | ||
this.listItemGroups.forEach((group) => { | ||
if (group.contains(item)) { | ||
heading += group.heading; |
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 I was thinking array because string concat would match two words from different headings combined. Can we change to use an array here?
@@ -649,6 +653,10 @@ export class List | |||
} | |||
} | |||
|
|||
private setListItemGroups(): void { | |||
this.listItemGroups = Array.from(this.el.querySelectorAll(listItemGroupSelector)); |
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 other alternative would be something like this:
let ancestor = el.closest("calcite-list-item-group");
const headings: string[] = [];
while (ancestor) {
headings.push(ancestor.heading);
ancestor = ancestor.closest("calcite-list-item-group");
}
I'm not sure which would be more performant, calling closest() a few times on connectedCallback or calling setListItemGroups
on each mutation. Thoughts @jcfranco?
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.
This looks good to me 👍
The querying of groups comment is pretty minor and would require storing that information on each item so what you currently have seems to make the most sense.
Related Issue: #7702
Summary
calcite-list-item-group
in filtering.heading
can be used asfilterProp
.