summaryrefslogtreecommitdiffstats
path: root/toolkit/content/widgets/panel-list
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/widgets/panel-list')
-rw-r--r--toolkit/content/widgets/panel-list/README.stories.md22
-rw-r--r--toolkit/content/widgets/panel-list/panel-list.css4
-rw-r--r--toolkit/content/widgets/panel-list/panel-list.js59
-rw-r--r--toolkit/content/widgets/panel-list/panel-list.stories.mjs45
4 files changed, 87 insertions, 43 deletions
diff --git a/toolkit/content/widgets/panel-list/README.stories.md b/toolkit/content/widgets/panel-list/README.stories.md
index b8800e2b5f..3e8617958e 100644
--- a/toolkit/content/widgets/panel-list/README.stories.md
+++ b/toolkit/content/widgets/panel-list/README.stories.md
@@ -6,7 +6,7 @@ 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: Nested menus are not currently supported. XUL is currently required to
+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`.
@@ -229,3 +229,23 @@ grow larger than its containing window if needed.
</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:
+
+```html
+<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.
diff --git a/toolkit/content/widgets/panel-list/panel-list.css b/toolkit/content/widgets/panel-list/panel-list.css
index 4358fc0cf8..619e6919a3 100644
--- a/toolkit/content/widgets/panel-list/panel-list.css
+++ b/toolkit/content/widgets/panel-list/panel-list.css
@@ -26,6 +26,10 @@
box-sizing: border-box;
}
+:host([has-submenu]) {
+ overflow-y: visible;
+}
+
:host(:not([slot=submenu])) {
max-height: 100%;
}
diff --git a/toolkit/content/widgets/panel-list/panel-list.js b/toolkit/content/widgets/panel-list/panel-list.js
index 1cc1f865c3..2e93b4ddc3 100644
--- a/toolkit/content/widgets/panel-list/panel-list.js
+++ b/toolkit/content/widgets/panel-list/panel-list.js
@@ -308,7 +308,7 @@
}
addHideListeners() {
- if (this.hasAttribute("stay-open") && !this.lastAnchorNode.hasSubmenu) {
+ if (this.hasAttribute("stay-open") && !this.lastAnchorNode?.hasSubmenu) {
// This is intended for inspection in Storybook.
return;
}
@@ -631,31 +631,12 @@
this.#defaultSlot = document.createElement("slot");
this.#defaultSlot.style.display = "none";
- if (this.hasSubmenu) {
- this.icon = document.createElement("div");
- this.icon.setAttribute("class", "submenu-icon");
- this.label.setAttribute("class", "submenu-label");
-
- this.button.setAttribute("class", "submenu-container");
- this.button.appendChild(this.icon);
-
- this.submenuSlot = document.createElement("slot");
- this.submenuSlot.name = "submenu";
-
- this.shadowRoot.append(
- style,
- this.button,
- this.#defaultSlot,
- this.submenuSlot
- );
- } else {
- this.shadowRoot.append(
- style,
- this.button,
- supportLinkSlot,
- this.#defaultSlot
- );
- }
+ this.shadowRoot.append(
+ style,
+ this.button,
+ supportLinkSlot,
+ this.#defaultSlot
+ );
}
connectedCallback() {
@@ -664,6 +645,10 @@
this._l10nRootConnected = true;
}
+ this.panel =
+ this.getRootNode()?.host?.closest("panel-list") ||
+ this.closest("panel-list");
+
if (!this.#initialized) {
this.#initialized = true;
// When click listeners are added to the panel-item it creates a node in
@@ -683,18 +668,28 @@
});
if (this.hasSubmenu) {
+ this.panel.setAttribute("has-submenu", "");
+ this.icon = document.createElement("div");
+ this.icon.setAttribute("class", "submenu-icon");
+ this.label.setAttribute("class", "submenu-label");
+
+ this.button.setAttribute("class", "submenu-container");
+ this.button.appendChild(this.icon);
+
+ this.submenuSlot = document.createElement("slot");
+ this.submenuSlot.name = "submenu";
+
+ this.shadowRoot.append(this.submenuSlot);
+
this.setSubmenuContents();
}
}
- this.panel =
- this.getRootNode()?.host?.closest("panel-list") ||
- this.closest("panel-list");
-
if (this.panel) {
this.panel.addEventListener("hidden", this);
this.panel.addEventListener("shown", this);
}
+
if (this.hasSubmenu) {
this.addEventListener("mouseenter", this);
this.addEventListener("mouseleave", this);
@@ -762,7 +757,9 @@
setSubmenuContents() {
this.submenuPanel = this.submenuSlot.assignedNodes()[0];
- this.shadowRoot.append(this.submenuPanel);
+ if (this.submenuPanel) {
+ this.shadowRoot.append(this.submenuPanel);
+ }
}
get disabled() {
diff --git a/toolkit/content/widgets/panel-list/panel-list.stories.mjs b/toolkit/content/widgets/panel-list/panel-list.stories.mjs
index 9c5a4cbe1f..db0ab7597c 100644
--- a/toolkit/content/widgets/panel-list/panel-list.stories.mjs
+++ b/toolkit/content/widgets/panel-list/panel-list.stories.mjs
@@ -22,6 +22,9 @@ panel-list-checked = Checked
panel-list-badged = Badged, look at me
panel-list-passwords = Passwords
panel-list-settings = Settings
+submenu-item-one = Submenu Item One
+submenu-item-two = Submenu Item Two
+submenu-item-three = Submenu Item Three
`,
},
};
@@ -36,7 +39,7 @@ function openMenu(event) {
}
}
-const Template = ({ isOpen, items, wideAnchor }) =>
+const Template = ({ isOpen, items, wideAnchor, hasSubMenu }) =>
html`
<style>
panel-item[icon="passwords"]::part(button) {
@@ -93,22 +96,36 @@ const Template = ({ isOpen, items, wideAnchor }) =>
?open=${isOpen}
?min-width-from-anchor=${wideAnchor}
>
- ${items.map(i =>
- i == "<hr>"
+ ${items.map((item, index) => {
+ // Always showing submenu on the first item for simplicity.
+ let showSubMenu = hasSubMenu && index == 0;
+ let subMenuId = showSubMenu ? "example-sub-menu" : undefined;
+ return item == "<hr>"
? html` <hr /> `
: html`
<panel-item
- icon=${i.icon ?? ""}
- ?checked=${i.checked}
- ?badged=${i.badged}
- accesskey=${ifDefined(i.accesskey)}
- data-l10n-id=${i.l10nId ?? i}
- ></panel-item>
- `
- )}
+ icon=${item.icon ?? ""}
+ ?checked=${item.checked}
+ ?badged=${item.badged}
+ accesskey=${ifDefined(item.accesskey)}
+ data-l10n-id=${item.l10nId ?? item}
+ submenu=${ifDefined(subMenuId)}
+ >
+ ${showSubMenu ? subMenuTemplate() : ""}
+ </panel-item>
+ `;
+ })}
</panel-list>
`;
+const subMenuTemplate = () => html`
+ <panel-list slot="submenu" id="example-sub-menu">
+ <panel-item data-l10n-id="submenu-item-one"></panel-item>
+ <panel-item data-l10n-id="submenu-item-two"></panel-item>
+ <panel-item data-l10n-id="submenu-item-three"></panel-item>
+ </panel-list>
+`;
+
export const Simple = Template.bind({});
Simple.args = {
isOpen: false,
@@ -145,3 +162,9 @@ Wide.args = {
...Simple.args,
wideAnchor: true,
};
+
+export const SubMenu = Template.bind({});
+SubMenu.args = {
+ ...Simple.args,
+ hasSubMenu: true,
+};