This adds a "checkbox" variant to panel-list/panel-item that sets the role to "menuitemcheckbox" and sets "aria-checked" correctly. The changes also prevent developers from using the checkmark background image unless the panel-item's type is "checkbox". Differential Revision: https://phabricator.services.mozilla.com/D219566 |
||
|---|---|---|
| .. | ||
| panel-item.css | ||
| panel-list.css | ||
| panel-list.js | ||
| panel-list.stories.mjs | ||
| README.stories.md | ||
Panel Menu
The panel-list and panel-item components work together to create a menu for
in-content contexts. The basic structure is a panel-list with panel-item
children and optional hr elements as separators. The panel-list will anchor
itself to the target of the initiating event when opened with
panelList.toggle(event).
Note: XUL is currently required to
support accesskey underlining (although using moz-label could change that).
Shortcuts are not displayed automatically in the panel-item.
This XUL requirement affects the "With Accesskeys" story, enabling or disabling accesskeys will not show any visual change in the story.
<panel-list stay-open open>
<panel-item action="new" accesskey="N">New</panel-item>
<panel-item accesskey="O">Open</panel-item>
<hr />
<panel-item action="save" accesskey="S">Save</panel-item>
<hr />
<panel-item accesskey="Q">Quit</panel-item>
</panel-list>
Status
Current status is listed as in-development since this is only intended for use
within in-content contexts. XUL is still required for accesskey underlining, but
could be migrated to use the moz-label component. This is a useful but
historical element that could likely use some attention at the API level and to
be brought up to our design systems standards.
When to use
- When there are multiple options for something that would take too much space with individual buttons.
- When the actions are not frequently needed.
- When you are within an in-content context.
When not to use
- When there is only one action.
- When the actions are frequently needed.
- In the browser chrome, you probably want to use menupopup or panel instead.
Basic usage
The source for panel-list can be found under
toolkit/content/widgets/panel-list.js.
You can find an examples of panel-list in use in the Firefox codebase in both
about:addons
and the
migration-wizard.
panel-list will automatically be imported in chrome documents, both through
markup and through JS with document.createElement("panel-list") or by cloning
a template.
<!-- This will import `panel-list` if needed in most cases. -->
<panel-list></panel-list>
In non-chrome documents it can be imported into .html/.xhtml files:
<script src="chrome://global/content/elements/panel-list.js"></script>
And used as follows:
<panel-list>
<panel-item accesskey="N">New</panel-item>
<panel-item accesskey="O">Open</panel-item>
<hr />
<panel-item accesskey="S">Save</panel-item>
<hr />
<panel-item accesskey="Q">Quit</panel-item>
</panel-list>
The toggle method takes the event you received on your anchor button and opens
the menu attached to that element.
anchorButton.addEventListener("mousedown", e => panelList.toggle(e));
Accesskeys are activated with the bare accesskey letter when the menu is opened.
So for this example after opening the menu pressing s will fire a click event
on the Save panel-item.
Note: XUL is currently required for accesskey underlining, but can be replaced
with moz-label later.
Fluent usage
The panel-item expects to have text content set by fluent.
<panel-list>
<panel-item data-l10n-id="menu-new"></panel-item>
<panel-item data-l10n-id="menu-save"></panel-item>
</panel-list>
In which case your Fluent messages will look something like this:
menu-new = New
.accesskey = N
menu-save = Save
.accesskey = S
Advanced usage
Showing the menu
By default the menu will be hidden. It is shown when the open attribute is
set, but that won't position the menu by default.
To trigger the auto-positioning of the menu, it should be opened or closed using
the toggle(event) method.
function onMenuButton(event) {
document.querySelector("panel-list").toggle(event);
}
The toggle(event) method will use event.target as the anchor for the menu.
To achieve the expected behaviour, the menu should open on mousedown for mouse
events, and click for keyboard events. This can be accomplished by checking
the event.inputSource property in chrome contexts or event.detail in
non-chrome contexts (event.detail will be the click count which is 0 when a
click is from the keyboard).
function openMenu(event) {
if (
event.type == "mousedown" ||
event.inputSource == MouseEvent.MOZ_SOURCE_KEYBOARD ||
!event.detail
) {
document.querySelector("panel-list").toggle(event);
}
}
let menuButton = document.getElementById("open-menu-button");
menuButton.addEventListener("mousedown", openMenu);
menuButton.addEventListener("click", openMenu);
Checkbox
Items may be used as a checkbox by setting the type attribute.
This allows checkmark icon to be automatically rendered and removed via the item's checked attribute.
Note: this item does not have its own click handler for adding or removing the checkmark, you must handle that.
<panel-list stay-open open>
<panel-item action="toggle-setting" type="checkbox" checked="">Toggle Setting</panel-item>
<panel-item action="toggle-panel" type="checkbox">Toggle Panel</panel-item>
</panel-list>
<panel-list>
<panel-item action="toggle-setting" type="checkbox" checked="">Toggle Setting</panel-item>
<panel-item action="toggle-panel" type="checkbox">Toggle Panel</panel-item>
</panel-list>
Icons
Icons can be added to the panel-items by setting a background-image on
panel-item::part(button).
panel-item[action="new"]::part(button) {
background-image: url("./new.svg");
}
panel-item[action="save"]::part(button) {
background-image: url("./save.svg");
}
Badging
Icons may be badged by setting the badged attribute. This adds a dot next to
the icon.
<panel-list>
<panel-item action="new">New</panel-item>
<panel-item action="save" badged>Save</panel-item>
</panel-list>
<panel-list stay-open open>
<panel-item action="new">New</panel-item>
<panel-item action="save" badged>Save</panel-item>
</panel-list>
Matching anchor width
When using the panel-list like a select dropdown, it's nice to have it match
the size of the anchor button. You can see this in practice in the
Wide variant and the
migration-wizard. Setting the min-width-from-anchor attribute will cause the
menu to match its anchor's width when it is opened.
<button class="current-selection">Apples</button>
<panel-list min-width-from-anchor>
<panel-item>Apples</panel-list>
<panel-item>Bananas</panel-list>
</panel-list>
Usage in a XUL panel
The "new" (as of early 2023) migration wizard uses the panel-list inside of a
XUL panel element to let its contents escape its container dialog by creating
an OS-level window. This can be useful if the menu could be larger than its
container, however in chrome contexts you are likely better off using
menupopup.
By placing a panel-list inside of a XUL panel it will automatically defer
its positioning responsibilities to the XUL panel and it will then be able to
grow larger than its containing window if needed.
<!-- Assuming we're in a XUL document. -->
<panel>
<html:panel-list>
<html:panel-item>Apples</html:panel-item>
<html:panel-item>Apples</html:panel-item>
<html:panel-item>Apples</html:panel-item>
</html:panel-list>
</panel>
Submenus
panel-list supports nested submenus. Submenus can be created by nesting a second panel-list in a panel-item's submenu slot and specifying a submenu attribute on that panel-item that points to the nested list's ID. For example:
<panel-list>
<panel-item>No submenu</panel-item>
<panel-item>No submenu</panel-item>
<panel-item submenu="example-submenu">
Has a submenu
<panel-list slot="submenu" id="example-submenu">
<panel-item>I'm a submenu item!</panel-item>
<panel-item>I'm also a submenu item!</panel-item>
</panel-list>
</panel-item>
</panel-list>
As of February 2024 submenus are only in use in Firefox View and support for nesting beyond one submenu may be limited.