Skip to content
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

Merged
merged 7 commits into from
Jun 7, 2017

Conversation

amsheehan
Copy link
Contributor

resolves #706

@amsheehan amsheehan force-pushed the fix/select-disabled-states branch from d5bdca6 to b5d3eb9 Compare June 2, 2017 19:44
@codecov-io
Copy link

codecov-io commented Jun 2, 2017

Codecov Report

Merging #780 into master will increase coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
packages/mdc-menu/simple/constants.js 100% <ø> (ø) ⬆️
packages/mdc-menu/simple/foundation.js 100% <100%> (ø) ⬆️
packages/mdc-menu/simple/index.js 100% <100%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 52856b0...adf5c4c. Read the comment docs.

@yeelan0319 yeelan0319 self-requested a review June 2, 2017 20:05
Copy link
Contributor

@traviskaufman traviskaufman left a 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.

A Menu Item
</li>
<li class="mdc-list-item mdc-list-item--disabled" role="menuitem" tabindex="-1" aria-hidden="true">
Another Menu Item
Copy link
Contributor

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.

Copy link
Contributor Author

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,
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super nit: Vegetables (Disabled)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me.

@@ -153,6 +155,16 @@
@include mdc-theme-prop(color, text-primary-on-dark);
}
}

&--disabled {
Copy link
Contributor

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.

Copy link
Contributor Author

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. 👍

@@ -136,6 +136,25 @@ classes:
| `mdc-simple-menu--open-from-bottom-right` | Open the menu from the bottom right. |


#### Disabled list items
Copy link
Contributor

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?

Copy link
Contributor Author

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?

@amsheehan
Copy link
Contributor Author

@traviskaufman the only thing that sticks in my craw with using mdc-list-item[aria-hidden="true"] in mdc-list.scss is that we'll have to use !important on every rule because of specificity and the order of the css in mdc-simple-menu.scss (it overrides a few mdc-list-item styles).

@traviskaufman
Copy link
Contributor

traviskaufman commented Jun 6, 2017

@amsheehan That wasn't what I was suggesting. I was suggesting that within mdc-simple-menu.scss we add some new style rules to .mdc-list-item when used within mdc-menu that styles them differently based on their aria-disabled (not aria-hidden) property. Wdyt?

@amsheehan
Copy link
Contributor Author

@traviskaufman Aha! Yes. I agree with that entirely. 👍

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">
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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.

Copy link
Contributor Author

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 {
Copy link
Contributor

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;
  }
}

Copy link
Contributor Author

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();
Copy link
Contributor

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...

Copy link
Contributor Author

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.

Copy link
Contributor Author

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

@@ -139,6 +139,8 @@

.mdc-select__menu {
.mdc-list-item {
user-select: none;
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@amsheehan amsheehan force-pushed the fix/select-disabled-states branch 2 times, most recently from 7ab1e87 to 962f28d Compare June 6, 2017 20:08
@amsheehan
Copy link
Contributor Author

amsheehan commented Jun 6, 2017

Alright, the biggest change in the latest commit is just that now we check for aria-disabled in both handlePossibleSelected_() and close() in the foundation. We do this so as not to use Event.stopPropagation() in handlePossibleSelected_().

  1. We check it in handlePossibleSelected_() to prevent the custom event from firing which eventually sets aria-selected after a delay (resulting in a disabled option with a selected appearance)
  2. We now have an optional parameter evt in close()'s signature. We also check for aria-disabled, and if it exists we return right away to prevent the select menu from closing.

@amsheehan amsheehan force-pushed the fix/select-disabled-states branch 2 times, most recently from 7fc80b9 to 75bf54c Compare June 7, 2017 16:19
td.verify(mockAdapter.deregisterDocumentClickHandler(td.matchers.anything()), {times: 0});
});

// const {foundation, mockAdapter} = setupTest();
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@amsheehan amsheehan force-pushed the fix/select-disabled-states branch from 75bf54c to e1e711c Compare June 7, 2017 17:50
@amsheehan amsheehan force-pushed the fix/select-disabled-states branch from e1e711c to afcc06f Compare June 7, 2017 18:39
@@ -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'. |
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Copy link
Contributor

@traviskaufman traviskaufman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@amsheehan amsheehan merged commit ef44d3d into master Jun 7, 2017
@amsheehan amsheehan deleted the fix/select-disabled-states branch June 7, 2017 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Select does not respect disabled property on items
4 participants