From 0a9f64d0acf3bb6326a54e289d93683210564e2e Mon Sep 17 00:00:00 2001 From: hjones Date: Wed, 17 Jul 2024 18:10:14 +0000 Subject: [PATCH] Bug 1858808 - add accesskey support to moz-button r=reusable-components-reviewers,tgiles Differential Revision: https://phabricator.services.mozilla.com/D216353 --- .../tests/widgets/test_moz_button.html | 76 +++++++++++++++++++ .../content/widgets/moz-button/moz-button.css | 5 +- .../content/widgets/moz-button/moz-button.mjs | 14 +++- .../widgets/moz-button/moz-button.stories.mjs | 18 ++++- 4 files changed, 110 insertions(+), 3 deletions(-) diff --git a/toolkit/content/tests/widgets/test_moz_button.html b/toolkit/content/tests/widgets/test_moz_button.html index c472ff7918d7..da7ccf73ea11 100644 --- a/toolkit/content/tests/widgets/test_moz_button.html +++ b/toolkit/content/tests/widgets/test_moz_button.html @@ -227,6 +227,82 @@ await new Promise(resolve => requestAnimationFrame(resolve)); verifyImageIcon(nine); }); + + add_task(async function testAccesskey() { + let firstButton = document.querySelector(".one"); + let secondButton = document.querySelector(".two"); + let accesskey = "t"; + let seenEvents = []; + + function trackEvent(event) { + seenEvents.push(event.type); + } + + [firstButton, secondButton].forEach(button => { + button.addEventListener("click", trackEvent); + }); + + firstButton.setAttribute("accesskey", accesskey); + await firstButton.updateComplete; + + firstButton.blur(); + isnot(document.activeElement, firstButton, "First button is not focused."); + isnot( + firstButton.shadowRoot.activeElement, + firstButton.buttonEl, + "Inner button element is not focused." + ); + + synthesizeKey( + accesskey, + navigator.platform.includes("Mac") + ? { altKey: true, ctrlKey: true } + : { altKey: true, shiftKey: true } + ); + + is( + document.activeElement, + firstButton, + "First button recieves focus after accesskey is pressed." + ); + is( + firstButton.shadowRoot.activeElement, + firstButton.buttonEl, + "Inner button input element is focused after accesskey is pressed." + ); + is(seenEvents.length, 1, "One event was triggered."); + is(seenEvents[0], "click", "The first button was clicked."); + + secondButton.setAttribute("accesskey", accesskey); + await secondButton.updateComplete; + + synthesizeKey( + accesskey, + navigator.platform.includes("Mac") + ? { altKey: true, ctrlKey: true } + : { altKey: true, shiftKey: true } + ); + + is( + document.activeElement, + secondButton, + "Focus cycles between buttons with the same accesskey." + ); + + synthesizeKey( + accesskey, + navigator.platform.includes("Mac") + ? { altKey: true, ctrlKey: true } + : { altKey: true, shiftKey: true } + ); + + is( + document.activeElement, + firstButton, + "Focus cycles between buttons with the same accesskey." + ); + is(seenEvents.length, 1, "No additional click events were triggered."); + }); diff --git a/toolkit/content/widgets/moz-button/moz-button.css b/toolkit/content/widgets/moz-button/moz-button.css index 4eb6839e063a..b404334de78f 100644 --- a/toolkit/content/widgets/moz-button/moz-button.css +++ b/toolkit/content/widgets/moz-button/moz-button.css @@ -26,7 +26,6 @@ button { display: flex; justify-content: center; align-items: center; - gap: var(--space-small); &[size=small] { min-height: var(--button-min-height-small); @@ -129,6 +128,10 @@ button { } } + &.labelled { + gap: var(--space-small) + } + &[type~=icon]:not(.labelled) { background-size: var(--icon-size-default); background-position: center; diff --git a/toolkit/content/widgets/moz-button/moz-button.mjs b/toolkit/content/widgets/moz-button/moz-button.mjs index fe27af7c5691..01135ff7a41e 100644 --- a/toolkit/content/widgets/moz-button/moz-button.mjs +++ b/toolkit/content/widgets/moz-button/moz-button.mjs @@ -5,6 +5,9 @@ import { html, ifDefined, classMap } from "../vendor/lit.all.mjs"; import { MozLitElement } from "../lit-utils.mjs"; +// eslint-disable-next-line import/no-unassigned-import +import "chrome://global/content/elements/moz-label.mjs"; + /** * A button with multiple types and two sizes. * @@ -49,6 +52,8 @@ export default class MozButton extends MozLitElement { ariaLabel: { type: String, state: true }, iconSrc: { type: String }, hasVisibleLabel: { type: Boolean, state: true }, + accessKeyAttribute: { type: String, attribute: "accesskey", reflect: true }, + accessKey: { type: String, state: true }, }; static queries = { @@ -73,6 +78,10 @@ export default class MozButton extends MozLitElement { this.ariaLabel = this.ariaLabelAttribute; this.ariaLabelAttribute = null; } + if (changes.has("accessKeyAttribute")) { + this.accessKey = this.accessKeyAttribute; + this.accessKeyAttribute = null; + } } // Delegate clicks on host to the button element. @@ -107,11 +116,14 @@ export default class MozButton extends MozLitElement { aria-label=${ifDefined(this.ariaLabel)} part="button" class=${classMap({ labelled: this.label || this.hasVisibleLabel })} + accesskey=${ifDefined(this.accessKey)} > ${this.iconSrc ? html`` : ""} - ${this.labelTemplate()} + `; } diff --git a/toolkit/content/widgets/moz-button/moz-button.stories.mjs b/toolkit/content/widgets/moz-button/moz-button.stories.mjs index f8a8dad81e82..710c03797a2c 100644 --- a/toolkit/content/widgets/moz-button/moz-button.stories.mjs +++ b/toolkit/content/widgets/moz-button/moz-button.stories.mjs @@ -44,14 +44,24 @@ moz-button-aria-labelled = }, }; -const Template = ({ type, size, l10nId, iconSrc, disabled }) => html` +const Template = ({ + type, + size, + l10nId, + iconSrc, + disabled, + accesskey, + clickHandler, +}) => html` `; @@ -106,3 +116,9 @@ IconText.args = { iconSrc: "chrome://global/skin/icons/edit-copy.svg", l10nId: "moz-button-labelled", }; +export const WithAccesskey = Template.bind({}); +WithAccesskey.args = { + ...Default.args, + accesskey: "t", + clickHandler: () => alert("Activating the accesskey clicks the button"), +};