-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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(menu): Add disabled list items to menu #780
Conversation
d5bdca6
to
b5d3eb9
Compare
Codecov Report
@@ Coverage Diff @@
## master #780 +/- ##
==========================================
+ Coverage 99.92% 99.92% +<.01%
==========================================
Files 65 65
Lines 2767 2776 +9
Branches 318 323 +5
==========================================
+ Hits 2765 2774 +9
Misses 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.
The issue I see here is that we are defining a modifier class for mdc-list-item
within mdc-menu
, which kind of breaks encapsulation across components. Ideally, any modifier classes defined for mdc-list-item
are defined within mdc-list
.
Furthermore, aria-hidden
seems incorrect in this context, since that attribute tells assistive technology that the element is "not visible or perceivable to any user"
Have you considered an approach of checking for aria-disabled instead of using a --disabled
modifier combined with aria-hidden
? You could then modify the new adapter method to be getAttributeForEventTarget
and check if aria-hidden
is set to 'true'
.
This has the added benefit of not introducing any modifier classes for mdc-list-item
.
Finally, since this adds a new adapter method, this needs to have a BREAKING CHANGE:
in the final commit message.
packages/mdc-menu/README.md
Outdated
A Menu Item | ||
</li> | ||
<li class="mdc-list-item mdc-list-item--disabled" role="menuitem" tabindex="-1" aria-hidden="true"> | ||
Another Menu 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.
Super nit: How about change the item name to be Disabled Menu Item
?
It will make it very clear that where the example 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.
👍
@@ -37,6 +37,7 @@ export default class MDCSimpleMenuFoundation extends MDCFoundation { | |||
removeClass: (/* className: string */) => {}, | |||
hasClass: (/* className: string */) => {}, | |||
hasNecessaryDom: () => /* boolean */ false, | |||
eventTargetHasClass: (/* target: EventTarget, className: string */) => false, |
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.
IMHO, we should make the method name more generic and straightforward about its intention. I feel elementHasClass: (/*element: DOMElement, className: string*/
sounds more clear when I implement adapter method because the method is opinion-less about whether it is a event target. And all event target are DOMElement based on MDN. WDYT?
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 we take this approach anywhere else. We refer to elements as eventTargets pretty much whenever an adapter method will interact with an event.
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.
+1 to @amsheehan's comment. The method is opinionless by design, because we don't want to make reference's to anything DOM-related within an foundation code, the primary place where an adapter is used.
packages/mdc-select/README.md
Outdated
Bread, Cereal, Rice, and Pasta | ||
</li> | ||
<li class="mdc-list-item mdc-list-item--disabled" role="option" id="vegetables" tabindex="-1" aria-disabled="true"> | ||
Vegetables |
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.
Super nit: Vegetables (Disabled)?
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.
Works for me.
packages/mdc-select/mdc-select.scss
Outdated
@@ -153,6 +155,16 @@ | |||
@include mdc-theme-prop(color, text-primary-on-dark); | |||
} | |||
} | |||
|
|||
&--disabled { |
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 if we extract this part and make it a mixin class in either mdc-simple-menu
or mdc-list
? Then we may remove the duplicated definition 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.
We no longer use this modifier class, nor do we set any style in mdc-list
for a disabled list item. 👍
packages/mdc-menu/README.md
Outdated
@@ -136,6 +136,25 @@ classes: | |||
| `mdc-simple-menu--open-from-bottom-right` | Open the menu from the bottom right. | | |||
|
|||
|
|||
#### Disabled list 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.
Shall we provide a method in menu to set menu item to disabled programmatically?
I think we could potentially use the ones that defined in mdc-select
, couldn't we?
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'm not fundamentally opposed to it. Especially since there is some language around a mechanism like that in the spec.
Menu items which are relevant but need certain conditions to be met may be disabled. For example, the Copy menu option becomes enabled when text is selected.
I don't know if that's what this story is concerned with though. @traviskaufman WDYT?
@traviskaufman the only thing that sticks in my craw with using |
@amsheehan That wasn't what I was suggesting. I was suggesting that within |
@traviskaufman Aha! Yes. I agree with that entirely. 👍 |
demos/select.html
Outdated
Pick a food group | ||
</li> | ||
<li class="mdc-list-item" role="option" tabindex="0"> | ||
Bread, Cereal, Rice, and Pasta | ||
</li> | ||
<li class="mdc-list-item" role="option" tabindex="0"> | ||
<li class="mdc-list-item" role="option" aria-disabled="true" tabindex="-1"> |
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 even need tabindex
attributes here? Or could we just remove them altogether?
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 just depends if we want users to be able to tab to disabled options/menu 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.
I believe that disabled items should not be focusable. Also, -1
for a tabindex doesn't allow users to tab to it; I believe it means that JS can focus()
it if need be. So yeah I feel like removing it would be the better option.
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'm convinced
} | ||
} | ||
|
||
.mdc-list-item[aria-disabled="true"]:focus::before { |
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: you could nest these
.mdc-list-item[aria-disabled="true"] {
&:focus::before,
&:active::before {
opacity: 0;
}
}
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.
👍
evt.target, MDCSimpleMenuFoundation.strings.ARIA_DISABLED_ATTR | ||
) === 'true'; | ||
if (targetIsDisabled) { | ||
evt.stopPropagation(); |
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, we probably just want to return
here. We've had lots of trouble with stopPropagation
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.
The other option is to pass the event to the close method and check if the target is disabled there. The problem is that there is a document click handler that closes the select that gets triggered if we let the event propagate.
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.
You know what, I just implemented it that way and I think it's better. That way the user can still listen for events on a disabled item and do whatever custom thing they may want.
@@ -189,6 +190,23 @@ $mdc-simple-menu-item-fade-duration: .3s; | |||
opacity: .18; | |||
} | |||
|
|||
.mdc-list-item[aria-disabled="true"] { | |||
cursor: default; |
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.
have we considered putting pointer-events: none
on here? That may also take care of not having to check in event handling logic whether the component is disabled.
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 I did consider it. The problem is the event still triggers handlers on the item's parent which causes selects to close when items with pointer-events: none
are clicked.
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.
Ahh of course. That makes sense.
packages/mdc-select/mdc-select.scss
Outdated
@@ -139,6 +139,8 @@ | |||
|
|||
.mdc-select__menu { | |||
.mdc-list-item { | |||
user-select: 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.
dunno if you need this because IIRC mdc-select__menu
is a mdc-simple-menu
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.
👍
7ab1e87
to
962f28d
Compare
Alright, the biggest change in the latest commit is just that now we check for
|
7fc80b9
to
75bf54c
Compare
td.verify(mockAdapter.deregisterDocumentClickHandler(td.matchers.anything()), {times: 0}); | ||
}); | ||
|
||
// const {foundation, mockAdapter} = setupTest(); |
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.
Super nit: May be remove it if we no longer use them?
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.
Ha. Not a super nit, super mistake on my part.
75bf54c
to
e1e711c
Compare
e1e711c
to
afcc06f
Compare
@@ -256,8 +275,8 @@ The adapter for temporary drawers must provide the following functions, with cor | |||
| `getNumberOfItems() => numbers` | Returns the number of _item_ elements inside the items container. In our vanilla component, we determine this by counting the number of list items whose `role` attribute corresponds to the correct child role of the role present on the menu list element. For example, if the list element has a role of `menu` this queries for all elements that have a role of `menuitem`. | | |||
| `registerInteractionHandler(type: string, handler: EventListener) => void` | Adds an event listener `handler` for event type `type`. | | |||
| `deregisterInteractionHandler(type: string, handler: EventListener) => void` | Removes an event listener `handler` for event type `type`. | | |||
| `registerDocumentClickHandler(handler: EventListener) => void` | Adds an event listener `handler` for event type 'click'. | | |||
| `deregisterDocumentClickHandler(handler: EventListener) => void` | Removes an event listener `handler` for event type 'click'. | | |||
| `registerBodyClickHandler(handler: EventListener) => void` | Adds an event listener `handler` for event type 'click'. | |
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.
N.B. This is now a breaking change due to the adapter API methods having changed, so we can't forget to include that info in the final commit message
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.
resolves #706