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 automation picker overflow menu for keyboard #21048

Merged
merged 5 commits into from
Jul 3, 2024

Conversation

karwosts
Copy link
Contributor

@karwosts karwosts commented Jun 9, 2024

Proposed change

The current overflow menu in a lot of the new data tables cannot be used with the keyboard, as far as I can tell. Both enter or space close the menu without doing anything, neither of them trigger the click event. That seems like it is only triggered for <a> links, and only for Enter.

Just reading https://material-web.dev/components/menu/ it seems like maybe @close-event could be a reasonable event to use instead of @click so it can have keyboard support? The only drawback is it is also triggered by the escape key, which I've added a helper event to prevent actions on that.

I'm not clear if this is intentional behavior of md-menu-item, could be slightly related to issue 5577 from github.com/material-components/material-web, but I'm not sure if a fix there would necessarily fix this either.

There are many menus that are affected by this, but I'll just start with this one to discuss a good pattern for a solution.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New feature (thank you!)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Example configuration

Additional information

Checklist

  • The code change is tested and works locally.
  • There is no commented out code in this PR.
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

Summary by CodeRabbit

  • New Features

    • Added event listeners to handle close-menu events in the navigation menu.
    • Introduced a clickAction property for custom actions in menu items.
  • Improvements

    • Enhanced clarity and context maintenance in automation picker by replacing direct event handlers with arrow functions.

@karwosts karwosts added the Accessibility Related to accessibility for people with disabilities label Jun 9, 2024

This comment was marked as off-topic.

coderabbitai[bot]

This comment was marked as off-topic.

@@ -898,6 +906,12 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
`;
}

private _handleMenuClose(ev) {
if (ev.detail.reason.key === "Escape") return;
const action = ev.currentTarget.action.bind(this);
Copy link
Member

@bramkragten bramkragten Jun 10, 2024

Choose a reason for hiding this comment

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

Instead of using bind, can we make the action functions arrow functions?

Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should use a switch/case with a string action to call the right function instead of binding the function on the element.

Copy link
Member

Choose a reason for hiding this comment

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

Instead of using target or currentTarget, use initiator defined in the custom event detail. Then I wonder if the key/reason check is even necessary as hopefully that would be null or undefined unless the user actually took the action with space or enter?

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 initiator is still ha-menu-item when canceling the menu with Escape key.

Copy link
Member

@steverep steverep left a comment

Choose a reason for hiding this comment

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

I think this proposed change is way too much of a hack. It also presumes that escape is the only way to close the menu, which may not be the case at all, and then lead to activation the user didn't take.

MDMenuItem extends MDListItem, and so my guess here would be that the simple fix should be to use the @request-activation event instead of @click. I'm dealing with some data loss of my docker volumes so I cannot test that ATM.

The same problem exists when listening to click instead of request-selected on mwc-list-item, so this is likely just a continuation of that with Material 3. I fixed many of those but many new ones using click have popped up or regressed. For the record, that's why I've taken a hiatus from a11y recently - I'm working on something big to hopefully prevent such regressions in the future.

@karwosts
Copy link
Contributor Author

I might be missing something, but listening for request-activation on a ha-menu-item does not seem to ever fire (not with mouse, not with any combination of keyboard keystrokes I can find)

Copy link
Member

@steverep steverep left a comment

Choose a reason for hiding this comment

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

If request-activation doesn't work, then I guess we're stuck with this approach until material-components/material-web#5577 gets resolved. To make it less fluffy on each instance, my suggestions are to move the crux of the handling to the element classes, and just require the instances to set the action to take.

src/panels/config/automation/ha-automation-picker.ts Outdated Show resolved Hide resolved
src/panels/config/automation/ha-automation-picker.ts Outdated Show resolved Hide resolved
src/panels/config/automation/ha-automation-picker.ts Outdated Show resolved Hide resolved
@@ -898,6 +906,12 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
`;
}

private _handleMenuClose(ev) {
if (ev.detail.reason.key === "Escape") return;
const action = ev.currentTarget.action.bind(this);
Copy link
Member

Choose a reason for hiding this comment

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

Instead of using target or currentTarget, use initiator defined in the custom event detail. Then I wonder if the key/reason check is even necessary as hopefully that would be null or undefined unless the user actually took the action with space or enter?

coderabbitai[bot]

This comment was marked as off-topic.

src/components/ha-menu.ts Outdated Show resolved Hide resolved
src/components/ha-menu.ts Show resolved Hide resolved
src/components/ha-menu.ts Outdated Show resolved Hide resolved
src/components/ha-menu.ts Outdated Show resolved Hide resolved
coderabbitai[bot]

This comment was marked as off-topic.

Copy link
Member

@steverep steverep left a comment

Choose a reason for hiding this comment

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

Other than the few suggestions below, LGTM. If you can swing it quick, I'll approve so we can fix this for 2024.7.


@customElement("ha-menu-item")
export class HaMenuItem extends MdMenuItem {
@property() closeAction?: (ev: CloseMenuEvent) => void;
Copy link
Member

Choose a reason for hiding this comment

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

A few suggestions to improve the API:

  1. Turn off the attribute. I don't know why lit-analyzer doesn't flag that function props shouldn't be attributes.
  2. Make the parameter the triggering item instead of the event, and make it optional as many handlers won't need it.
  3. To make the purpose much clearer where used in instances, maybe rename to clickAction or just action. @bramkragten any strong opinion here?
Suggested change
@property() closeAction?: (ev: CloseMenuEvent) => void;
@property({ attribute: false }) clickAction?: (item?: HTMLElement) => void;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What should be passed as the item here, the ev.detail.initiator?

Copy link
Member

Choose a reason for hiding this comment

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

Yes

src/components/ha-menu.ts Show resolved Hide resolved
src/components/ha-menu.ts Show resolved Hide resolved
@@ -1051,28 +1051,28 @@ class HaAutomationPicker extends SubscribeMixin(LitElement) {
this._applyFilters();
}

private _showInfo(ev) {
const automation = ev.currentTarget.parentElement.anchorElement.automation;
private _showInfo = (ev) => {
Copy link
Member

Choose a reason for hiding this comment

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

...then type the parameter as HaMenuItem (trying to cut down on the way too many instances of implicit any types we have).

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 will say that typing this is getting kind of ugly. I have to cast the parent element, then cast the anchorElement back to any as the automation property does not actually exist on any type, it's just a custom added thing here.

const automation = ((item.parentElement as HaMenu)!.anchorElement as any)!.automation;

Is this going to be acceptable or need to clean it up somehow?

Copy link
Member

Choose a reason for hiding this comment

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

I think it's fine for now. The best goal would be to turn on noImplicitAny in tsconfig. Casting as any is never great, but that's a different rule for another day.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Outside diff range comments (4)
src/panels/config/automation/ha-automation-picker.ts (4)

Line range hint 1078-1100: Avoid non-null assertions and any type.

Consider using optional chaining and proper type annotations for better type safety.

- const automation = ((item.parentElement as HaMenu)!.anchorElement as any)!.automation;
+ const automation = (item.parentElement as HaMenu)?.anchorElement?.automation as AutomationEntity;
Tools
Biome

[error] 1056-1056: Forbidden non-null assertion.

Unsafe fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator

(lint/style/noNonNullAssertion)


[error] 1056-1056: Forbidden non-null assertion.

Unsafe fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator

(lint/style/noNonNullAssertion)


[error] 1056-1056: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)


[error] 1062-1062: Forbidden non-null assertion.

Unsafe fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator

(lint/style/noNonNullAssertion)


[error] 1062-1062: Forbidden non-null assertion.

Unsafe fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator

(lint/style/noNonNullAssertion)


[error] 1062-1062: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)


[error] 1072-1072: Forbidden non-null assertion.

Unsafe fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator

(lint/style/noNonNullAssertion)


[error] 1072-1072: Forbidden non-null assertion.

Unsafe fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator

(lint/style/noNonNullAssertion)


[error] 1072-1072: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)


[error] 1079-1079: Forbidden non-null assertion.

Unsafe fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator

(lint/style/noNonNullAssertion)


[error] 1079-1079: Forbidden non-null assertion.

Unsafe fix: Replace with optional chain operator ?. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator

(lint/style/noNonNullAssertion)


[error] 1079-1079: Unexpected any. Specify a different type.

any disables many type checking rules. Its use should be avoided.

(lint/suspicious/noExplicitAny)


Line range hint 1102-1117: Avoid non-null assertions and any type.

Consider using optional chaining and proper type annotations for better type safety.

- const automation = ((item.parentElement as HaMenu)!.anchorElement as any)!.automation;
+ const automation = (item.parentElement as HaMenu)?.anchorElement?.automation as AutomationEntity;

Line range hint 1129-1146: Avoid non-null assertions and any type.

Consider using optional chaining and proper type annotations for better type safety.

- const automation = ((item.parentElement as HaMenu)!.anchorElement as any)!.automation;
+ const automation = (item.parentElement as HaMenu)?.anchorElement?.automation as AutomationEntity;

Line range hint 1166-1192: Avoid non-null assertions and any type.

Consider using optional chaining and proper type annotations for better type safety.

- const automation = ((item.parentElement as HaMenu)!.anchorElement as any)!.automation;
+ const automation = (item.parentElement as HaMenu)?.anchorElement?.automation as AutomationEntity;

src/components/ha-menu.ts Show resolved Hide resolved
src/components/ha-menu.ts Show resolved Hide resolved
@steverep steverep merged commit e332364 into home-assistant:dev Jul 3, 2024
13 checks passed
@steverep steverep added this to the 2024.7 milestone Jul 3, 2024
@karwosts karwosts deleted the auto-picker-menu-fix-keyboard branch July 3, 2024 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Accessibility Related to accessibility for people with disabilities cla-signed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants