1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-24 00:23:17 +00:00

PM-21620 finalize a11y UX concerns for option selection (#14777)

* PM-21620 finalize a11y UX concerns for option selection

* SR should announce that the button has a menu popup collapsed and expanded when they open it

* support up and down keys -close menu when other menu expanded

* dynamic aria label

* type safety

* instanceOf to replace as Node for type

* default aria hidden prop that can be overridden

* update mock and make message more descriptive

* Update apps/browser/src/autofill/content/components/icons/collection-shared.ts

Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com>

---------

Co-authored-by: Jonathan Prusik <jprusik@users.noreply.github.com>
This commit is contained in:
Daniel Riera
2025-05-15 13:32:05 -04:00
committed by GitHub
parent b30faeb62b
commit 82d0925f4e
20 changed files with 187 additions and 37 deletions

View File

@@ -1090,13 +1090,24 @@
},
"notificationLoginSaveConfirmation": {
"message": "saved to Bitwarden.",
"description": "Shown to user after item is saved."
},
"notificationLoginUpdatedConfirmation": {
"message": "updated in Bitwarden.",
"description": "Shown to user after item is updated."
},
"selectItemAriaLabel": {
"message": "Select $ITEMTYPE$, $ITEMNAME$",
"description": "Used by screen readers. $1 is the item type (like vault or folder), $2 is the selected item name.",
"placeholders": {
"itemType": {
"content": "$1"
},
"itemName": {
"content": "$2"
}
}
},
"saveAsNewLoginAction": {
"message": "Save as new login",
"description": "Button text for saving login details as a new entry."
@@ -3585,7 +3596,7 @@
"orgTrustWarning1": {
"message": "This organization has an Enterprise policy that will enroll you in account recovery. Enrollment will allow organization administrators to change your password. Only proceed if you recognize this organization and the fingerprint phrase displayed below matches the organization's fingerprint."
},
"trustUser":{
"trustUser": {
"message": "Trust user"
},
"sendsNoItemsTitle": {
@@ -5325,4 +5336,4 @@
"noPermissionsViewPage": {
"message": "You do not have permissions to view this page. Try logging in with a different account."
}
}
}

View File

@@ -33,6 +33,9 @@ export function OptionSelectionButton({
class=${selectionButtonStyles({ disabled, toggledOn, theme })}
title=${text}
type="button"
aria-haspopup="menu"
aria-expanded=${toggledOn}
aria-controls="option-menu"
@click=${handleButtonClick}
>
${buttonIcon ?? nothing}

View File

@@ -11,6 +11,7 @@ export type IconProps = {
color?: string;
disabled?: boolean;
theme: Theme;
ariaHidden?: boolean;
};
export type Option = {

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function AngleDown({ color, disabled, theme }: IconProps) {
export function AngleDown({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 8" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 8"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M13.53.47a.75.75 0 0 0-1.06 0L7 5.94 1.53.47A.75.75 0 1 0 .47 1.53l6 6a.75.75 0 0 0 1.06 0l6-6a.75.75 0 0 0 0-1.06Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function AngleUp({ color, disabled, theme }: IconProps) {
export function AngleUp({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 8" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 8"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M.47 7.53a.75.75 0 0 0 1.06 0L7 2.06l5.47 5.47a.75.75 0 1 0 1.06-1.06l-6-6a.75.75 0 0 0-1.06 0l-6 6a.75.75 0 0 0 0 1.06Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Business({ color, disabled, theme }: IconProps) {
export function Business({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 16" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 12 16"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M3.25 3a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5ZM7.25 3a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5ZM7.25 6a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5ZM6.5 9.75A.75.75 0 0 1 7.25 9h1.5a.75.75 0 0 1 0 1.5h-1.5a.75.75 0 0 1-.75-.75ZM2.5 6.75A.75.75 0 0 1 3.25 6h1.5a.75.75 0 0 1 0 1.5h-1.5a.75.75 0 0 1-.75-.75ZM3.25 9a.75.75 0 0 0 0 1.5h1.5a.75.75 0 0 0 0-1.5h-1.5Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Close({ color, disabled, theme }: IconProps) {
export function Close({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 14"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M.22.22a.75.75 0 0 1 1.06 0L7 5.94 12.72.22a.75.75 0 1 1 1.06 1.06L8.06 7l5.72 5.72a.75.75 0 1 1-1.06 1.06L7 8.06l-5.72 5.72a.75.75 0 0 1-1.06-1.06L5.94 7 .22 1.28a.75.75 0 0 1 0-1.06Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function CollectionShared({ color, disabled, theme }: IconProps) {
export function CollectionShared({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 14"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M3.5.75A.75.75 0 0 1 4.25 0h5.5a.75.75 0 0 1 0 1.5h-5.5A.75.75 0 0 1 3.5.75ZM2.25 2a.75.75 0 0 0 0 1.5h9.5a.75.75 0 0 0 0-1.5h-9.5ZM6 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM10 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM7 11.46a1.928 1.928 0 0 0-.586-1.386 2.035 2.035 0 0 0-2.828 0A1.928 1.928 0 0 0 3 11.461c0 .298.241.539.54.539h2.92a.54.54 0 0 0 .54-.54ZM8 11.46a2.928 2.928 0 0 0-.371-1.426A2.005 2.005 0 0 1 9 9.5a2.035 2.035 0 0 1 1.414.574A1.928 1.928 0 0 1 11 11.461a.54.54 0 0 1-.54.539H7.904c.063-.168.097-.35.097-.54Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function ExclamationTriangle({ color, disabled, theme }: IconProps) {
export function ExclamationTriangle({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 15" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 15"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M9 11C9 11.5523 8.55229 12 8 12C7.44772 12 7 11.5523 7 11C7 10.4477 7.44772 10 8 10C8.55229 10 9 10.4477 9 11Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function ExternalLink({ color, disabled, theme }: IconProps) {
export function ExternalLink({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 14"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M1.5 2.75c0-.69.56-1.25 1.25-1.25h3.5a.75.75 0 0 0 0-1.5h-3.5A2.75 2.75 0 0 0 0 2.75v8.5A2.75 2.75 0 0 0 2.75 14h8.5A2.75 2.75 0 0 0 14 11.25v-3.5a.75.75 0 0 0-1.5 0v3.5c0 .69-.56 1.25-1.25 1.25h-8.5c-.69 0-1.25-.56-1.25-1.25v-8.5Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Family({ color, disabled, theme }: IconProps) {
export function Family({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
fill-rule="evenodd"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Folder({ color, disabled, theme }: IconProps) {
export function Folder({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 13" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 13"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M2 0a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8L6.586.586A2 2 0 0 0 5.172 0H2Zm5.379 3.5L5.525 1.646a.5.5 0 0 0-.353-.146H2a.5.5 0 0 0-.5.5v9a.5.5 0 0 0 .5.5h12a.5.5 0 0 0 .5-.5V4a.5.5 0 0 0-.5-.5H7.379Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Globe({ color, disabled, theme }: IconProps) {
export function Globe({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0Zm0 14.5c.23 0 .843-.226 1.487-1.514.524-1.048.906-2.526.994-4.236H5.519c.088 1.71.47 3.188.994 4.236C7.157 14.274 7.77 14.5 8 14.5ZM5.52 7.25h4.96c-.087-1.71-.47-3.188-.993-4.236C8.843 1.726 8.23 1.5 8 1.5c-.23 0-.843.226-1.487 1.514C5.99 4.062 5.607 5.54 5.52 7.25Zm6.463 0h2.474a6.506 6.506 0 0 0-3.766-5.168c.718 1.305 1.197 3.125 1.292 5.168Zm-7.966 0c.095-2.043.574-3.863 1.292-5.168A6.506 6.506 0 0 0 1.543 7.25h2.474Zm7.966 1.5c-.095 2.043-.574 3.863-1.292 5.168a6.506 6.506 0 0 0 3.766-5.168h-2.474Zm-6.677 5.185c-.718-1.305-1.197-3.125-1.292-5.168H1.54a6.506 6.506 0 0 0 3.766 5.168Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function PencilSquare({ color, disabled, theme }: IconProps) {
export function PencilSquare({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 15" fill="none" aria-hidden="true">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 15 15"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M11.013.677a1.75 1.75 0 0 1 2.474 0l.836.836a1.75 1.75 0 0 1 0 2.475L9.03 9.28a.75.75 0 0 1-.348.197l-3 .75a.75.75 0 0 1-.91-.91l.75-3a.75.75 0 0 1 .198-.348L11.013.677Zm1.414 1.06a.25.25 0 0 0-.354 0l-.646.647a.75.75 0 0 1 .103.086l1 1a.751.751 0 0 1 .087.103l.646-.646a.25.25 0 0 0 0-.353l-.836-.836Zm-.854 2.88a.752.752 0 0 1-.103-.087l-1-1a.756.756 0 0 1-.087-.103L6.928 6.884 6.531 8.47l1.586-.397 3.456-3.456Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function Shield({ color, theme }: IconProps) {
export function Shield({ ariaHidden = true, color, theme }: IconProps) {
const shapeColor = color || themes[theme].brandLogo;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 16" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 16"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M13.469.2A.647.647 0 0 0 13 0H1a.639.639 0 0 0-.468.2.641.641 0 0 0-.2.468v8a4.81 4.81 0 0 0 .348 1.777c.216.557.507 1.083.865 1.563.367.48.779.925 1.229 1.329.417.383.857.741 1.317 1.073.4.284.82.553 1.26.807.44.254.75.425.932.515.183.09.33.16.44.208.087.041.181.062.277.06a.58.58 0 0 0 .27-.063c.113-.05.259-.118.444-.208s.5-.262.932-.515c.432-.253.857-.523 1.26-.807.46-.332.9-.69 1.319-1.073.45-.404.861-.849 1.228-1.33.357-.48.648-1.005.865-1.562a4.79 4.79 0 0 0 .348-1.777v-8A.63.63 0 0 0 13.47.2Zm-1.547 8.54c0 2.9-4.921 5.392-4.921 5.392V1.714h4.92v7.027Z"

View File

@@ -4,11 +4,16 @@ import { html } from "lit";
import { IconProps } from "../common-types";
import { buildIconColorRule, ruleNames, themes } from "../constants/styles";
export function User({ color, disabled, theme }: IconProps) {
export function User({ ariaHidden = true, color, disabled, theme }: IconProps) {
const shapeColor = disabled ? themes[theme].secondary["300"] : color || themes[theme].text.main;
return html`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 15" fill="none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 14 15"
fill="none"
aria-hidden="${ariaHidden}"
>
<path
class=${css(buildIconColorRule(shapeColor, ruleNames.fill))}
d="M9.203 7.339a4 4 0 1 0-4.407 0A7.033 7.033 0 0 0 2.05 8.953a6.655 6.655 0 0 0-1.517 2.162A6.393 6.393 0 0 0 0 13.667C0 14.403.597 15 1.333 15h11.334c.736 0 1.333-.597 1.333-1.333 0-.876-.181-1.743-.533-2.552a6.654 6.654 0 0 0-1.517-2.162 7.032 7.032 0 0 0-2.747-1.614ZM9.5 4a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0Zm2.592 7.714c.247.57.384 1.175.405 1.786H1.503a4.897 4.897 0 0 1 .405-1.786 5.156 5.156 0 0 1 1.177-1.675 5.534 5.534 0 0 1 1.787-1.136A5.805 5.805 0 0 1 7 8.5c.732 0 1.456.137 2.128.403.673.265 1.28.652 1.787 1.136a5.156 5.156 0 0 1 1.177 1.675Z"

View File

@@ -132,6 +132,7 @@ export const mockI18n = {
saveFailure: "Error saving",
saveFailureDetails: "Oh no! We couldn't save this. Try entering the details manually.",
saveLogin: "Save login",
selectItemAriaLabel: "Select $ITEMTYPE$, $ITEMNAME$",
typeLogin: "Login",
updateLoginAction: "Update login",
updateLogin: "Update existing login",

View File

@@ -13,11 +13,19 @@ const { css } = createEmotion({
});
export type OptionItemProps = Option & {
contextLabel?: string;
theme: Theme;
handleSelection: () => void;
};
export function OptionItem({ icon, text, value, theme, handleSelection }: OptionItemProps) {
export function OptionItem({
contextLabel,
icon,
text,
theme,
value,
handleSelection,
}: OptionItemProps) {
const handleSelectionKeyUpProxy = (event: KeyboardEvent) => {
const listenedForKeys = new Set(["Enter", "Space"]);
if (listenedForKeys.has(event.code) && event.target instanceof Element) {
@@ -29,12 +37,18 @@ export function OptionItem({ icon, text, value, theme, handleSelection }: Option
const iconProps: IconProps = { color: themes[theme].text.main, theme };
const itemIcon = icon?.(iconProps);
const ariaLabel =
contextLabel && text
? chrome.i18n.getMessage("selectItemAriaLabel", [contextLabel, text])
: text;
return html`<div
class=${optionItemStyles}
key=${value}
tabindex="0"
title=${text}
role="option"
aria-label=${ariaLabel}
@click=${handleSelection}
@keyup=${handleSelectionKeyUpProxy}
>

View File

@@ -33,17 +33,41 @@ export function OptionItems({
const isSafari = false;
return html`
<div class=${optionsStyles({ theme, topOffset })} key="container">
<div
class=${optionsStyles({ theme, topOffset })}
key="container"
@keyup=${(e: KeyboardEvent) => handleMenuKeyUp(e)}
>
${label ? html`<div class=${optionsLabelStyles({ theme })}>${label}</div>` : nothing}
<div class=${optionsWrapper({ isSafari, theme })}>
${options.map((option) =>
OptionItem({ ...option, theme, handleSelection: () => handleOptionSelection(option) }),
OptionItem({
...option,
theme,
contextLabel: label,
handleSelection: () => handleOptionSelection(option),
}),
)}
</div>
</div>
`;
}
function handleMenuKeyUp(event: KeyboardEvent) {
const items = [
...(event.currentTarget as HTMLElement).querySelectorAll<HTMLElement>('[tabindex="0"]'),
];
const index = items.indexOf(document.activeElement as HTMLElement);
const direction = event.key === "ArrowDown" ? 1 : event.key === "ArrowUp" ? -1 : 0;
if (index === -1 || direction === 0) {
return;
}
event.preventDefault();
items[(index + direction + items.length) % items.length]?.focus();
}
const optionsStyles = ({ theme, topOffset }: { theme: Theme; topOffset: number }) => css`
${typography.body1}

View File

@@ -48,10 +48,18 @@ export class OptionSelection extends LitElement {
@state()
private selection?: Option;
private handleButtonClick = (event: Event) => {
private static currentOpenInstance: OptionSelection | null = null;
private handleButtonClick = async (event: Event) => {
if (!this.disabled) {
// Menu is about to be shown
if (!this.showMenu) {
const isOpening = !this.showMenu;
if (isOpening) {
if (OptionSelection.currentOpenInstance && OptionSelection.currentOpenInstance !== this) {
OptionSelection.currentOpenInstance.showMenu = false;
}
OptionSelection.currentOpenInstance = this;
this.menuTopOffset = this.offsetTop;
// Distance from right edge of button to left edge of the viewport
@@ -71,9 +79,29 @@ export class OptionSelection extends LitElement {
optionsMenuItemMaxWidth + optionItemIconWidth + 2 + 8 + 12 * 2;
this.menuIsEndJustified = distanceFromViewportRightEdge < maxDifferenceThreshold;
} else {
if (OptionSelection.currentOpenInstance === this) {
OptionSelection.currentOpenInstance = null;
}
}
this.showMenu = !this.showMenu;
this.showMenu = isOpening;
if (this.showMenu) {
await this.updateComplete;
const firstItem = this.querySelector('#option-menu [tabindex="0"]') as HTMLElement;
firstItem?.focus();
}
}
};
private handleFocusOut = (event: FocusEvent) => {
const relatedTarget = event.relatedTarget;
if (!(relatedTarget instanceof Node) || !this.contains(relatedTarget)) {
this.showMenu = false;
if (OptionSelection.currentOpenInstance === this) {
OptionSelection.currentOpenInstance = null;
}
}
};
@@ -95,7 +123,10 @@ export class OptionSelection extends LitElement {
}
return html`
<div class=${optionSelectionStyles({ menuIsEndJustified: this.menuIsEndJustified })}>
<div
class=${optionSelectionStyles({ menuIsEndJustified: this.menuIsEndJustified })}
@focusout=${this.handleFocusOut}
>
${OptionSelectionButton({
disabled: this.disabled,
icon: this.selection?.icon,