summaryrefslogtreecommitdiffstats
path: root/toolkit/content/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/widgets')
-rw-r--r--toolkit/content/widgets/arrowscrollbox.js15
-rw-r--r--toolkit/content/widgets/autocomplete-input.js10
-rw-r--r--toolkit/content/widgets/autocomplete-popup.js6
-rw-r--r--toolkit/content/widgets/autocomplete-richlistitem.js4
-rw-r--r--toolkit/content/widgets/browser-custom-element.js4
-rw-r--r--toolkit/content/widgets/datetimebox.js2
-rw-r--r--toolkit/content/widgets/dialog.js4
-rw-r--r--toolkit/content/widgets/editor.js6
-rw-r--r--toolkit/content/widgets/findbar.js12
-rw-r--r--toolkit/content/widgets/menu.js2
-rw-r--r--toolkit/content/widgets/menulist.js3
-rw-r--r--toolkit/content/widgets/menupopup.js23
-rw-r--r--toolkit/content/widgets/moz-button-group/moz-button-group.mjs2
-rw-r--r--toolkit/content/widgets/moz-button/moz-button.css142
-rw-r--r--toolkit/content/widgets/moz-button/moz-button.mjs90
-rw-r--r--toolkit/content/widgets/moz-button/moz-button.stories.mjs100
-rw-r--r--toolkit/content/widgets/moz-input-box.js2
-rw-r--r--toolkit/content/widgets/moz-label/README.stories.md4
-rw-r--r--toolkit/content/widgets/moz-label/moz-label.mjs2
-rw-r--r--toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs2
-rw-r--r--toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs6
-rw-r--r--toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css123
-rw-r--r--toolkit/content/widgets/moz-page-nav/moz-page-nav.css76
-rw-r--r--toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs170
-rw-r--r--toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs77
-rw-r--r--toolkit/content/widgets/moz-support-link/moz-support-link.mjs2
-rw-r--r--toolkit/content/widgets/moz-toggle/moz-toggle.css39
-rw-r--r--toolkit/content/widgets/named-deck.js2
-rw-r--r--toolkit/content/widgets/notificationbox.js2
-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
-rw-r--r--toolkit/content/widgets/radio.js6
-rw-r--r--toolkit/content/widgets/richlistbox.js2
-rw-r--r--toolkit/content/widgets/search-textbox.js2
-rw-r--r--toolkit/content/widgets/tabbox.js8
-rw-r--r--toolkit/content/widgets/text.js2
-rw-r--r--toolkit/content/widgets/textrecognition.js2
-rw-r--r--toolkit/content/widgets/tree.js8
-rw-r--r--toolkit/content/widgets/videocontrols.js80
-rw-r--r--toolkit/content/widgets/wizard.js2
42 files changed, 993 insertions, 181 deletions
diff --git a/toolkit/content/widgets/arrowscrollbox.js b/toolkit/content/widgets/arrowscrollbox.js
index 28de96c8d7..7109891faf 100644
--- a/toolkit/content/widgets/arrowscrollbox.js
+++ b/toolkit/content/widgets/arrowscrollbox.js
@@ -21,7 +21,7 @@
return `
<html:link rel="stylesheet" href="chrome://global/skin/toolbarbutton.css"/>
<html:link rel="stylesheet" href="chrome://global/skin/arrowscrollbox.css"/>
- <toolbarbutton id="scrollbutton-up" part="scrollbutton-up" keyNav="false"/>
+ <toolbarbutton id="scrollbutton-up" part="scrollbutton-up" keyNav="false" data-l10n-id="overflow-scroll-button-up"/>
<spacer part="overflow-start-indicator"/>
<box class="scrollbox-clip" part="scrollbox-clip" flex="1">
<scrollbox part="scrollbox" flex="1">
@@ -29,7 +29,7 @@
</scrollbox>
</box>
<spacer part="overflow-end-indicator"/>
- <toolbarbutton id="scrollbutton-down" part="scrollbutton-down" keyNav="false"/>
+ <toolbarbutton id="scrollbutton-down" part="scrollbutton-down" keyNav="false" data-l10n-id="overflow-scroll-button-down"/>
`;
}
@@ -43,6 +43,8 @@
this._scrollButtonDown =
this.shadowRoot.getElementById("scrollbutton-down");
+ MozXULElement.insertFTLIfNeeded("toolkit/global/arrowscrollbox.ftl");
+
this._arrowScrollAnim = {
scrollbox: this,
requestHandle: 0,
@@ -134,6 +136,8 @@
}
this.hasConnected = true;
+ document.l10n.connectRoot(this.shadowRoot);
+
if (!this.hasAttribute("smoothscroll")) {
this.smoothScroll = Services.prefs.getBoolPref(
"toolkit.scrollbox.smoothScroll",
@@ -639,6 +643,7 @@
this._scrollTimer.cancel();
this._scrollTimer = null;
}
+ document.l10n.disconnectRoot(this.shadowRoot);
}
on_wheel(event) {
@@ -749,7 +754,7 @@
}
}
- on_touchend(event) {
+ on_touchend() {
this._touchStart = -1;
}
@@ -804,12 +809,12 @@
this._updateScrollButtonsDisabledState();
}
- on_scroll(event) {
+ on_scroll() {
this._isScrolling = true;
this._updateScrollButtonsDisabledState();
}
- on_scrollend(event) {
+ on_scrollend() {
this._isScrolling = false;
this._destination = 0;
this._direction = 0;
diff --git a/toolkit/content/widgets/autocomplete-input.js b/toolkit/content/widgets/autocomplete-input.js
index 36105ba4d7..a6fb4b5067 100644
--- a/toolkit/content/widgets/autocomplete-input.js
+++ b/toolkit/content/widgets/autocomplete-input.js
@@ -40,7 +40,7 @@
this.addEventListener(
"compositionstart",
- event => {
+ () => {
if (
this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
) {
@@ -52,7 +52,7 @@
this.addEventListener(
"compositionend",
- event => {
+ () => {
if (
this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
) {
@@ -64,7 +64,7 @@
this.addEventListener(
"focus",
- event => {
+ () => {
this.attachController();
if (
window.gBrowser &&
@@ -82,7 +82,7 @@
this.addEventListener(
"blur",
- event => {
+ () => {
if (!this._dontBlur) {
if (this.forceComplete && this.mController.matchCount >= 1) {
// If forceComplete is requested, we need to call the enter processing
@@ -625,7 +625,7 @@
return value;
}
- onInput(aEvent) {
+ onInput() {
if (
!this.mIgnoreInput &&
this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
diff --git a/toolkit/content/widgets/autocomplete-popup.js b/toolkit/content/widgets/autocomplete-popup.js
index f033511e07..a13ba1bc62 100644
--- a/toolkit/content/widgets/autocomplete-popup.js
+++ b/toolkit/content/widgets/autocomplete-popup.js
@@ -572,7 +572,7 @@
}
setListeners() {
- this.addEventListener("popupshowing", event => {
+ this.addEventListener("popupshowing", () => {
// If normalMaxRows wasn't already set by the input, then set it here
// so that we restore the correct number when the popup is hidden.
@@ -584,14 +584,14 @@
this.mPopupOpen = true;
});
- this.addEventListener("popupshown", event => {
+ this.addEventListener("popupshown", () => {
if (this._adjustHeightOnPopupShown) {
this._adjustHeightOnPopupShown = false;
this.adjustHeight();
}
});
- this.addEventListener("popuphiding", event => {
+ this.addEventListener("popuphiding", () => {
var isListActive = true;
if (this.selectedIndex == -1) {
isListActive = false;
diff --git a/toolkit/content/widgets/autocomplete-richlistitem.js b/toolkit/content/widgets/autocomplete-richlistitem.js
index ccbd37e132..fddd5b4029 100644
--- a/toolkit/content/widgets/autocomplete-richlistitem.js
+++ b/toolkit/content/widgets/autocomplete-richlistitem.js
@@ -21,7 +21,7 @@
* This overrides listitem's mousedown handler because we want to set the
* selected item even when the shift or accel keys are pressed.
*/
- this.addEventListener("mousedown", event => {
+ this.addEventListener("mousedown", () => {
// Call this.control only once since it's not a simple getter.
let control = this.control;
if (!control || control.disabled) {
@@ -587,7 +587,7 @@
/**
* Override _getSearchTokens to have the Learn More text emphasized
*/
- _getSearchTokens(aSearch) {
+ _getSearchTokens() {
return [this._learnMoreString.toLowerCase()];
}
}
diff --git a/toolkit/content/widgets/browser-custom-element.js b/toolkit/content/widgets/browser-custom-element.js
index e9b29034fa..887f59c742 100644
--- a/toolkit/content/widgets/browser-custom-element.js
+++ b/toolkit/content/widgets/browser-custom-element.js
@@ -872,7 +872,7 @@
this.webProgress.removeProgressListener(aListener);
}
- onPageHide(aEvent) {
+ onPageHide() {
// If we're browsing from the tab crashed UI to a URI that keeps
// this browser non-remote, we'll handle that here.
lazy.SessionStore?.maybeExitCrashedState(this);
@@ -1919,7 +1919,7 @@
// Called immediately after changing remoteness. If this method returns
// `true`, Gecko will assume frontend handled resuming the load, and will
// not attempt to resume the load itself.
- afterChangeRemoteness(browser, redirectLoadSwitchId) {
+ afterChangeRemoteness() {
/* no-op unless replaced */
return false;
}
diff --git a/toolkit/content/widgets/datetimebox.js b/toolkit/content/widgets/datetimebox.js
index 28b32fddfa..04ed398bd7 100644
--- a/toolkit/content/widgets/datetimebox.js
+++ b/toolkit/content/widgets/datetimebox.js
@@ -5,7 +5,7 @@
"use strict";
// This is a UA widget. It runs in per-origin UA widget scope,
-// to be loaded by UAWidgetsChild.jsm.
+// to be loaded by UAWidgetsChild.sys.mjs.
/*
* This is the class of entry. It will construct the actual implementation
diff --git a/toolkit/content/widgets/dialog.js b/toolkit/content/widgets/dialog.js
index 52eb2168f8..c4b25c2f48 100644
--- a/toolkit/content/widgets/dialog.js
+++ b/toolkit/content/widgets/dialog.js
@@ -146,7 +146,7 @@
if (document.readyState == "complete") {
this._postLoadInit();
} else {
- window.addEventListener("load", event => this._postLoadInit());
+ window.addEventListener("load", () => this._postLoadInit());
}
}
@@ -521,7 +521,7 @@
}
var btn = this.getButton(this.defaultButton);
- if (btn) {
+ if (btn && !btn.hidden) {
this._doButtonCommand(this.defaultButton);
}
}
diff --git a/toolkit/content/widgets/editor.js b/toolkit/content/widgets/editor.js
index 9e5ffb542e..8e014f77af 100644
--- a/toolkit/content/widgets/editor.js
+++ b/toolkit/content/widgets/editor.js
@@ -16,13 +16,13 @@
"nsIURIContentListener",
"nsISupportsWeakReference",
]),
- doContent(contentType, isContentPreferred, request, contentHandler) {
+ doContent() {
return false;
},
- isPreferred(contentType, desiredContentType) {
+ isPreferred() {
return false;
},
- canHandleContent(contentType, isContentPreferred, desiredContentType) {
+ canHandleContent() {
return false;
},
loadCookie: null,
diff --git a/toolkit/content/widgets/findbar.js b/toolkit/content/widgets/findbar.js
index 3cbce11771..cdfbd315a7 100644
--- a/toolkit/content/widgets/findbar.js
+++ b/toolkit/content/widgets/findbar.js
@@ -164,7 +164,7 @@
window.addEventListener("unload", this.destroy);
- this._findField.addEventListener("input", event => {
+ this._findField.addEventListener("input", () => {
// We should do nothing during composition. E.g., composing string
// before converting may matches a forward word of expected word.
// After that, even if user converts the composition string to the
@@ -230,17 +230,17 @@
}
});
- this._findField.addEventListener("blur", event => {
+ this._findField.addEventListener("blur", () => {
// Note: This code used to remove the selection
// if it matched an editable.
this.browser.finder.enableSelection();
});
- this._findField.addEventListener("focus", event => {
+ this._findField.addEventListener("focus", () => {
this._updateBrowserWithState();
});
- this._findField.addEventListener("compositionstart", event => {
+ this._findField.addEventListener("compositionstart", () => {
// Don't close the find toolbar while IME is composing.
let findbar = this;
findbar._isIMEComposing = true;
@@ -251,7 +251,7 @@
}
});
- this._findField.addEventListener("compositionend", event => {
+ this._findField.addEventListener("compositionend", () => {
this._isIMEComposing = false;
if (this.findMode != this.FIND_NORMAL) {
this._setFindCloseTimeout();
@@ -1307,7 +1307,7 @@
}
}
- onHighlightFinished(result) {
+ onHighlightFinished() {
// Noop.
}
diff --git a/toolkit/content/widgets/menu.js b/toolkit/content/widgets/menu.js
index f787747a01..1a55d799b6 100644
--- a/toolkit/content/widgets/menu.js
+++ b/toolkit/content/widgets/menu.js
@@ -129,7 +129,7 @@
};
// The <menucaption> element is used for rendering <html:optgroup> inside of <html:select>,
- // See SelectParentHelper.jsm.
+ // See SelectParentHelper.sys.mjs.
class MozMenuCaption extends MozMenuBaseMixin(MozXULElement) {
static get inheritedAttributes() {
return {
diff --git a/toolkit/content/widgets/menulist.js b/toolkit/content/widgets/menulist.js
index 4e66c030f3..3672d4ccf1 100644
--- a/toolkit/content/widgets/menulist.js
+++ b/toolkit/content/widgets/menulist.js
@@ -289,6 +289,9 @@
}
setInitialSelection() {
+ if (this.getAttribute("noinitialselection") === "true") {
+ return;
+ }
var popup = this.menupopup;
if (popup) {
var arr = popup.getElementsByAttribute("selected", "true");
diff --git a/toolkit/content/widgets/menupopup.js b/toolkit/content/widgets/menupopup.js
index 31801d6a33..c7448d4f05 100644
--- a/toolkit/content/widgets/menupopup.js
+++ b/toolkit/content/widgets/menupopup.js
@@ -11,16 +11,12 @@
"resource://gre/modules/AppConstants.sys.mjs"
);
- // For the non-native context menu styling, we need to know if we need
- // a gutter for checkboxes. To do this, check whether there are any
- // radio/checkbox type menuitems in a menupopup when showing it. We use a
- // system bubbling event listener to ensure we run *after* the "normal"
- // popupshowing listeners, so (visibility) changes they make to their items
- // take effect first, before we check for checkable menuitems.
- Services.els.addSystemEventListener(
- document,
+ document.addEventListener(
"popupshowing",
function (e) {
+ // For the non-native context menu styling, we need to know if we need
+ // a gutter for checkboxes. To do this, check whether there are any
+ // radio/checkbox type menuitems in a menupopup when showing it.
if (e.target.nodeName == "menupopup") {
let haveCheckableChild = e.target.querySelector(
`:scope > menuitem:not([hidden]):is([type=checkbox],[type=radio]${
@@ -33,7 +29,10 @@
e.target.toggleAttribute("needsgutter", haveCheckableChild);
}
},
- false
+ // we use a system bubbling event listener to ensure we run *after* the
+ // "normal" popupshowing listeners, so (visibility) changes they make to
+ // their items take effect first, before we check for checkable menuitems.
+ { mozSystemGroup: true }
);
class MozMenuPopup extends MozElements.MozElementMixin(XULPopupElement) {
@@ -74,13 +73,13 @@
initShadowDOM() {
// Retarget events from shadow DOM arrowscrollbox to the host.
- this.scrollBox.addEventListener("scroll", ev =>
+ this.scrollBox.addEventListener("scroll", () =>
this.dispatchEvent(new Event("scroll"))
);
- this.scrollBox.addEventListener("overflow", ev =>
+ this.scrollBox.addEventListener("overflow", () =>
this.dispatchEvent(new Event("overflow"))
);
- this.scrollBox.addEventListener("underflow", ev =>
+ this.scrollBox.addEventListener("underflow", () =>
this.dispatchEvent(new Event("underflow"))
);
}
diff --git a/toolkit/content/widgets/moz-button-group/moz-button-group.mjs b/toolkit/content/widgets/moz-button-group/moz-button-group.mjs
index 0706f94762..8bf553c23d 100644
--- a/toolkit/content/widgets/moz-button-group/moz-button-group.mjs
+++ b/toolkit/content/widgets/moz-button-group/moz-button-group.mjs
@@ -44,7 +44,7 @@ export default class MozButtonGroup extends MozLitElement {
}
}
- onSlotchange(e) {
+ onSlotchange() {
for (let child of this.defaultSlotEl.assignedNodes()) {
if (!(child instanceof Element)) {
// Text nodes won't support classList or getAttribute.
diff --git a/toolkit/content/widgets/moz-button/moz-button.css b/toolkit/content/widgets/moz-button/moz-button.css
new file mode 100644
index 0000000000..47567df41d
--- /dev/null
+++ b/toolkit/content/widgets/moz-button/moz-button.css
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+:host {
+ display: inline-block;
+}
+
+button {
+ appearance: none;
+ min-height: var(--button-min-height);
+ color: var(--button-text-color);
+ border: var(--button-border);
+ border-radius: var(--button-border-radius);
+ background-color: var(--button-background-color);
+ padding: var(--button-padding);
+ /* HTML button gets `font: -moz-button` from UA styles,
+ * but we want it to match the root font styling. */
+ font: inherit;
+ font-weight: var(--button-font-weight);
+ /* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */
+ font-size: var(--button-font-size);
+ width: 100%;
+
+ &[size=small] {
+ min-height: var(--button-min-height-small);
+ font-size: var(--button-font-size-small);
+ }
+
+ &:hover {
+ background-color: var(--button-background-color-hover);
+ border-color: var(--button-border-color-hover);
+ color: var(--button-text-color-hover);
+ }
+
+ &:hover:active {
+ background-color: var(--button-background-color-active);
+ border-color: var(--button-border-color-active);
+ color: var(--button-text-color-active);
+ }
+
+ &:disabled {
+ background-color: var(--button-background-color-disabled);
+ border-color: var(--button-border-color-disabled);
+ color: var(--button-text-color-disabled);
+ opacity: var(--button-opacity-disabled);
+ }
+
+ &:focus-visible {
+ outline: var(--focus-outline);
+ outline-offset: var(--focus-outline-offset);
+ }
+
+ &[type="primary"] {
+ background-color: var(--button-background-color-primary);
+ border-color: var(--button-border-color-primary);
+ color: var(--button-text-color-primary);
+
+ &:hover {
+ background-color: var(--button-background-color-primary-hover);
+ border-color: var(--button-border-color-primary-hover);
+ color: var(--button-text-color-primary-hover);
+ }
+
+ &:hover:active {
+ background-color: var(--button-background-color-primary-active);
+ border-color: var(--button-border-color-primary-active);
+ color: var(--button-text-color-primary-active);
+ }
+
+ &:disabled {
+ background-color: var(--button-background-color-primary-disabled);
+ border-color: var(--button-border-color-primary-disabled);
+ color: var(--button-text-color-primary-disabled);
+ }
+ }
+
+ &[type="destructive"] {
+ background-color: var(--button-background-color-destructive);
+ border-color: var(--button-border-color-destructive);
+ color: var(--button-text-color-destructive);
+
+ &:hover {
+ background-color: var(--button-background-color-destructive-hover);
+ border-color: var(--button-border-color-destructive-hover);
+ color: var(--button-text-color-destructive-hover);
+ }
+
+ &:hover:active {
+ background-color: var(--button-background-color-destructive-active);
+ border-color: var(--button-border-color-destructive-active);
+ color: var(--button-text-color-destructive-active);
+ }
+
+ &:disabled {
+ background-color: var(--button-background-color-destructive-disabled);
+ border-color: var(--button-border-color-destructive-disabled);
+ color: var(--button-text-color-destructive-disabled);
+ }
+ }
+
+ &[type~=ghost] {
+ background-color: var(--button-background-color-ghost);
+ border-color: var(--button-border-color-ghost);
+ color: var(--button-text-color-ghost);
+
+ &:hover {
+ background-color: var(--button-background-color-ghost-hover);
+ border-color: var(--button-border-color-ghost-hover);
+ color: var(--button-text-color-ghost-hover);
+ }
+
+ &:hover:active {
+ background-color: var(--button-background-color-ghost-active);
+ border-color: var(--button-border-color-ghost-active);
+ color: var(--button-text-color-ghost-active);
+ }
+
+ &:disabled {
+ background-color: var(--button-background-color-ghost-disabled);
+ border-color: var(--button-border-color-ghost-disabled);
+ color: var(--button-text-color-ghost-disabled);
+ }
+ }
+
+ &[type~=icon] {
+ background-size: var(--icon-size-default);
+ background-position: center;
+ background-repeat: no-repeat;
+ -moz-context-properties: fill, stroke;
+ fill: currentColor;
+ stroke: currentColor;
+ width: var(--button-size-icon);
+ height: var(--button-size-icon);
+ padding: var(--button-padding-icon);
+
+ &[size=small] {
+ width: var(--button-size-icon-small);
+ height: var(--button-size-icon-small);
+ }
+ }
+}
diff --git a/toolkit/content/widgets/moz-button/moz-button.mjs b/toolkit/content/widgets/moz-button/moz-button.mjs
new file mode 100644
index 0000000000..3e7c151e61
--- /dev/null
+++ b/toolkit/content/widgets/moz-button/moz-button.mjs
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html, ifDefined } from "../vendor/lit.all.mjs";
+import { MozLitElement } from "../lit-utils.mjs";
+
+/**
+ * A button with multiple types and two sizes.
+ *
+ * @tagname moz-button
+ * @property {string} label - The button's label, will be overridden by slotted content.
+ * @property {string} type - The button type.
+ * Options: default, primary, destructive, icon, icon ghost, ghost.
+ * @property {string} size - The button size.
+ * Options: default, small.
+ * @property {boolean} disabled - The disabled state.
+ * @property {string} title - The button's title attribute, used in shadow DOM and therefore not as an attribute on moz-button.
+ * @property {string} titleAttribute - Internal, map title attribute to the title JS property.
+ * @property {string} tooltipText - Set the title property, the title attribute will be used first.
+ * @property {string} ariaLabel - The button's arial-label attribute, used in shadow DOM and therefore not as an attribute on moz-button.
+ * @property {string} ariaLabelAttribute - Internal, map aria-label attribute to the ariaLabel JS property.
+ * @property {HTMLButtonElement} buttonEl - The internal button element in the shadow DOM.
+ * @slot default - The button's content, overrides label property.
+ * @fires click - The click event.
+ */
+export default class MozButton extends MozLitElement {
+ static shadowRootOptions = {
+ ...MozLitElement.shadowRootOptions,
+ delegatesFocus: true,
+ };
+
+ static properties = {
+ label: { type: String, reflect: true },
+ type: { type: String, reflect: true },
+ size: { type: String, reflect: true },
+ disabled: { type: Boolean, reflect: true },
+ title: { type: String, state: true },
+ titleAttribute: { type: String, attribute: "title", reflect: true },
+ tooltipText: { type: String },
+ ariaLabelAttribute: {
+ type: String,
+ attribute: "aria-label",
+ reflect: true,
+ },
+ ariaLabel: { type: String, state: true },
+ };
+
+ static queries = {
+ buttonEl: "button",
+ };
+
+ constructor() {
+ super();
+ this.type = "default";
+ this.size = "default";
+ this.disabled = false;
+ }
+
+ willUpdate(changes) {
+ if (changes.has("titleAttribute")) {
+ this.title = this.titleAttribute;
+ this.titleAttribute = null;
+ }
+ if (changes.has("ariaLabelAttribute")) {
+ this.ariaLabel = this.ariaLabelAttribute;
+ this.ariaLabelAttribute = null;
+ }
+ }
+
+ render() {
+ return html`
+ <link
+ rel="stylesheet"
+ href="chrome://global/content/elements/moz-button.css"
+ />
+ <button
+ type=${this.type}
+ size=${this.size}
+ ?disabled=${this.disabled}
+ title=${ifDefined(this.title || this.tooltipText)}
+ aria-label=${ifDefined(this.ariaLabel)}
+ part="button"
+ >
+ <slot>${this.label}</slot>
+ </button>
+ `;
+ }
+}
+customElements.define("moz-button", MozButton);
diff --git a/toolkit/content/widgets/moz-button/moz-button.stories.mjs b/toolkit/content/widgets/moz-button/moz-button.stories.mjs
new file mode 100644
index 0000000000..52a459e807
--- /dev/null
+++ b/toolkit/content/widgets/moz-button/moz-button.stories.mjs
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html } from "../vendor/lit.all.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "./moz-button.mjs";
+
+export default {
+ title: "UI Widgets/Moz Button",
+ component: "moz-button",
+ argTypes: {
+ l10nId: {
+ options: [
+ "moz-button-labelled",
+ "moz-button-titled",
+ "moz-button-aria-labelled",
+ ],
+ control: { type: "select" },
+ },
+ size: {
+ options: ["default", "small"],
+ control: { type: "radio" },
+ },
+ },
+ parameters: {
+ actions: {
+ handles: ["click"],
+ },
+ status: "in-development",
+ fluent: `
+moz-button-labelled = Button
+moz-button-primary = Primary
+moz-button-destructive = Destructive
+moz-button-titled =
+ .title = View logins
+moz-button-aria-labelled =
+ .aria-label = View logins
+`,
+ },
+};
+
+const Template = ({ type, size, l10nId, iconUrl, disabled }) => html`
+ <style>
+ moz-button[type~="icon"]::part(button) {
+ background-image: url("${iconUrl}");
+ }
+ </style>
+ <moz-button
+ data-l10n-id=${l10nId}
+ type=${type}
+ size=${size}
+ ?disabled=${disabled}
+ ></moz-button>
+`;
+
+export const Default = Template.bind({});
+Default.args = {
+ type: "default",
+ size: "default",
+ l10nId: "moz-button-labelled",
+ iconUrl: "chrome://global/skin/icons/more.svg",
+ disabled: false,
+};
+export const DefaultSmall = Template.bind({});
+DefaultSmall.args = {
+ type: "default",
+ size: "small",
+ l10nId: "moz-button-labelled",
+ iconUrl: "chrome://global/skin/icons/more.svg",
+ disabled: false,
+};
+export const Primary = Template.bind({});
+Primary.args = {
+ ...Default.args,
+ type: "primary",
+ l10nId: "moz-button-primary",
+};
+export const Destructive = Template.bind({});
+Destructive.args = {
+ ...Default.args,
+ type: "destructive",
+ l10nId: "moz-button-destructive",
+};
+export const Icon = Template.bind({});
+Icon.args = {
+ ...Default.args,
+ type: "icon",
+ l10nId: "moz-button-titled",
+};
+export const IconSmall = Template.bind({});
+IconSmall.args = {
+ ...Icon.args,
+ size: "small",
+};
+export const IconGhost = Template.bind({});
+IconGhost.args = {
+ ...Icon.args,
+ type: "icon ghost",
+};
diff --git a/toolkit/content/widgets/moz-input-box.js b/toolkit/content/widgets/moz-input-box.js
index 4704db6dc5..6e7b7b3f29 100644
--- a/toolkit/content/widgets/moz-input-box.js
+++ b/toolkit/content/widgets/moz-input-box.js
@@ -92,7 +92,7 @@
});
if (this.spellcheck) {
- this.menupopup.addEventListener("popuphiding", event => {
+ this.menupopup.addEventListener("popuphiding", () => {
if (this.spellCheckerUI) {
this.spellCheckerUI.clearSuggestionsFromMenu();
this.spellCheckerUI.clearDictionaryListFromMenu();
diff --git a/toolkit/content/widgets/moz-label/README.stories.md b/toolkit/content/widgets/moz-label/README.stories.md
index a3492ebefa..f5e4e2dd14 100644
--- a/toolkit/content/widgets/moz-label/README.stories.md
+++ b/toolkit/content/widgets/moz-label/README.stories.md
@@ -3,10 +3,10 @@
`moz-label` is an extension of the built-in `HTMLLabelElement` that provides accesskey styling and formatting as well as some click handling logic.
```html story
-<label is="moz-label" accesskey="c" for="check">
+<label is="moz-label" accesskey="c" for="check" style={{ display: "inline-block" }}>
This is a label with an accesskey:
</label>
-<input id="check" type="checkbox" defaultChecked />
+<input id="check" type="checkbox" defaultChecked style={{ display: "inline-block" }} />
```
Accesskey underlining is enabled by default on Windows and Linux. It is also enabled in Storybook on Mac for demonstrative purposes, but is usually controlled by the `ui.key.menuAccessKey` preference.
diff --git a/toolkit/content/widgets/moz-label/moz-label.mjs b/toolkit/content/widgets/moz-label/moz-label.mjs
index 7812436ecd..cd9144e7cc 100644
--- a/toolkit/content/widgets/moz-label/moz-label.mjs
+++ b/toolkit/content/widgets/moz-label/moz-label.mjs
@@ -103,7 +103,7 @@ class MozTextLabel extends HTMLLabelElement {
this.formatAccessKey();
}
- _onClick(event) {
+ _onClick() {
let controlElement = this.labeledControlElement;
if (!controlElement || this.disabled) {
return;
diff --git a/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs b/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
index 58f41c28e4..d83de5d29f 100644
--- a/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
+++ b/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
@@ -69,7 +69,7 @@ export default class MozMessageBar extends MozLitElement {
this.dismissable = false;
}
- onSlotchange(e) {
+ onSlotchange() {
let actions = this.actionsSlotEl.assignedNodes();
this.actionsEl.classList.toggle("active", actions.length);
}
diff --git a/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs b/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs
index 65803eed9f..6f19c45aee 100644
--- a/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs
+++ b/toolkit/content/widgets/moz-message-bar/moz-message-bar.stories.mjs
@@ -121,3 +121,9 @@ WithSupportLink.args = {
hasSupportLink: true,
hasActionButton: false,
};
+
+export const WithHeading = Template.bind({});
+WithHeading.args = {
+ ...Default.args,
+ l10nId: "moz-message-bar-message-heading",
+};
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css
new file mode 100644
index 0000000000..2975bb1a7c
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css
@@ -0,0 +1,123 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+:host {
+ border-radius: var(--border-radius-small);
+ &:focus-visible {
+ outline-offset: var(--page-nav-focus-outline-inset);
+ }
+}
+
+button {
+ background-color: var(--page-nav-button-background-color);
+ border: 1px solid var(--page-nav-border-color);
+ -moz-context-properties: fill, fill-opacity;
+ fill: currentColor;
+ display: grid;
+ grid-template-columns: min-content 1fr;
+ gap: 12px;
+ align-items: center;
+ font-size: inherit;
+ width: 100%;
+ font-weight: normal;
+ border-radius: var(--page-nav-button-border-radius);
+ color: var(--page-nav-button-text-color);
+ text-align: start;
+ transition: background-color 150ms;
+ padding: var(--page-nav-button-padding);
+}
+
+button:hover {
+ cursor: pointer;
+}
+
+@media not (prefers-contrast) {
+ button {
+ border-inline-start: 2px solid transparent;
+ border-inline-end: none;
+ border-block: none;
+ }
+
+ button:hover,
+ button[selected]:hover {
+ background-color: var(--page-nav-button-background-color-hover);
+ }
+
+ button[selected]:hover {
+ border-inline-start-color: inherit;
+ }
+
+ button[selected],
+ button[selected]:hover {
+ border-inline-start: 2px solid;
+ }
+
+ button[selected]:not(:focus-visible) {
+ border-start-start-radius: 0;
+ border-end-start-radius: 0;
+ }
+
+ button[selected]:not(:hover) {
+ color: var(--color-accent-primary);
+ background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 5%, transparent);
+ border-inline-start-color: var(--color-accent-primary);
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ button[selected] {
+ background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 12%, transparent);
+ }
+}
+
+button:focus-visible,
+button[selected]:focus-visible {
+ outline: var(--focus-outline);
+ outline-offset: var(--focus-outline-offset);
+ border-radius: var(--border-radius-small);
+}
+
+.page-nav-icon {
+ height: 20px;
+ width: 20px;
+ -moz-context-properties: fill;
+ fill: currentColor;
+}
+
+@media (prefers-contrast) {
+ button {
+ transition: none;
+ border-color: ButtonText;
+ background-color: var(--button-background-color);
+ }
+
+ button:hover {
+ color: SelectedItem;
+ }
+
+ button[selected] {
+ color: SelectedItemText;
+ background-color: SelectedItem;
+ border-color: SelectedItem;
+ }
+}
+
+slot {
+ font-size: var(--font-size-large);
+ margin: 0;
+ padding-inline-start: 0;
+ user-select: none;
+}
+
+@media (max-width: 52rem) {
+ button {
+ grid-template-columns: min-content;
+ justify-content: center;
+ margin-inline: 0;
+ }
+
+ slot {
+ display: none;
+ }
+}
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css
new file mode 100644
index 0000000000..49000f622d
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+:host {
+ --page-nav-button-border-radius: var(--button-border-radius);
+ --page-nav-button-text-color: var(--button-text-color);
+ --page-nav-button-background-color: transparent;
+ --page-nav-button-background-color-hover: var(--button-background-color-hover);
+ --page-nav-button-background-color-selected: var(--color-accent-primary);
+ --page-nav-button-padding: var(--space-small);
+ --page-nav-margin-top: 72px;
+ --page-nav-margin-bottom: 36px;
+ --page-nav-gap: 25px;
+ --page-nav-button-gap: var(--space-xsmall);
+ --page-nav-border-color: var(--border-color);
+ --page-nav-focus-outline-inset: var(--focus-outline-inset);
+ --page-nav-width: 240px;
+ margin-inline-start: 42px;
+ position: sticky;
+ top: 0;
+ height: 100vh;
+ width: var(--page-nav-width);
+
+ @media (prefers-reduced-motion) {
+ /* (See Bug 1610081) Setting border-inline-end to add clear differentiation between side navigation and main content area */
+ border-inline-end: 1px solid var(--page-nav-border-color);
+ }
+
+ @media (max-width: 52rem) {
+ grid-template-rows: 1fr auto;
+ --page-nav-width: 118px;
+ }
+}
+
+nav {
+ display: grid;
+ grid-template-rows: min-content 1fr auto;
+ gap: var(--page-nav-gap);
+ margin-block: var(--page-nav-margin-top) var(--page-nav-margin-bottom);
+ height: calc(100vh - var(--page-nav-margin-top) - var(--page-nav-margin-bottom));
+ @media (max-width: 52rem) {
+ grid-template-rows: 1fr auto;
+ }
+}
+
+.page-nav-header {
+ /* Align the header text/icon with the page nav button icons */
+ margin-inline-start: var(--page-nav-button-padding);
+ font-size: var(--font-size-xlarge);
+ font-weight: var(--font-weight-bold);
+ margin-block: 0;
+
+ @media (max-width: 52rem) {
+ display: none;
+ }
+}
+
+.primary-nav-group,
+#secondary-nav-group {
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-auto-rows: min-content;
+ gap: var(--page-nav-button-gap);
+
+ @media (max-width: 52rem) {
+ justify-content: center;
+ grid-template-columns: min-content;
+ }
+}
+
+@media (prefers-contrast) {
+ .primary-nav-group {
+ gap: var(--space-small);
+ }
+}
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs
new file mode 100644
index 0000000000..f998ee735f
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html } from "chrome://global/content/vendor/lit.all.mjs";
+import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
+
+/**
+ * A grouping of navigation buttons that is displayed at the page level,
+ * intended to change the selected view, provide a heading, and have links
+ * to external resources.
+ *
+ * @tagname moz-page-nav
+ * @property {string} currentView - The currently selected view.
+ * @property {string} heading - A heading to be displayed at the top of the navigation.
+ * @slot [default] - Used to append moz-page-nav-button elements to the navigation.
+ */
+export default class MozPageNav extends MozLitElement {
+ static properties = {
+ currentView: { type: String },
+ heading: { type: String },
+ };
+
+ static queries = {
+ headingEl: "#page-nav-header",
+ primaryNavGroupSlot: ".primary-nav-group slot",
+ secondaryNavGroupSlot: "#secondary-nav-group slot",
+ };
+
+ get pageNavButtons() {
+ return this.primaryNavGroupSlot
+ .assignedNodes()
+ .filter(
+ node => node?.localName === "moz-page-nav-button" && !node.hidden
+ );
+ }
+
+ onChangeView(e) {
+ this.currentView = e.target.view;
+ }
+
+ handleFocus(e) {
+ if (e.key == "ArrowDown" || e.key == "ArrowRight") {
+ e.preventDefault();
+ this.focusNextView();
+ } else if (e.key == "ArrowUp" || e.key == "ArrowLeft") {
+ e.preventDefault();
+ this.focusPreviousView();
+ }
+ }
+
+ focusPreviousView() {
+ let pageNavButtons = this.pageNavButtons;
+ let currentIndex = pageNavButtons.findIndex(b => b.selected);
+ let prev = pageNavButtons[currentIndex - 1];
+ if (prev) {
+ prev.activate();
+ prev.buttonEl.focus();
+ }
+ }
+
+ focusNextView() {
+ let pageNavButtons = this.pageNavButtons;
+ let currentIndex = pageNavButtons.findIndex(b => b.selected);
+ let next = pageNavButtons[currentIndex + 1];
+ if (next) {
+ next.activate();
+ next.buttonEl.focus();
+ }
+ }
+
+ render() {
+ return html`
+ <link
+ rel="stylesheet"
+ href="chrome://global/content/elements/moz-page-nav.css"
+ />
+ <nav>
+ <h1 class="page-nav-header" id="page-nav-header">${this.heading}</h1>
+ <div
+ class="primary-nav-group"
+ role="tablist"
+ aria-orientation="vertical"
+ aria-labelledby="page-nav-header"
+ >
+ <slot
+ @change-view=${this.onChangeView}
+ @keydown=${this.handleFocus}
+ ></slot>
+ </div>
+ <div id="secondary-nav-group" role="group">
+ <slot name="secondary-nav" @keydown=${this.handleFocus}></slot>
+ </div>
+ </nav>
+ `;
+ }
+
+ updated() {
+ let isViewSelected = false;
+ let assignedPageNavButtons = this.pageNavButtons;
+ for (let button of assignedPageNavButtons) {
+ button.selected = button.view == this.currentView;
+ isViewSelected = isViewSelected || button.selected;
+ }
+ if (!isViewSelected && assignedPageNavButtons.length) {
+ // Current page nav has no matching view, reset to the first view.
+ assignedPageNavButtons[0].activate();
+ }
+ }
+}
+customElements.define("moz-page-nav", MozPageNav);
+
+/**
+ * A navigation button intended to change the selected view within a page.
+ *
+ * @tagname moz-page-nav-button
+ * @property {string} iconSrc - The chrome:// url for the icon used for the button.
+ * @property {string} l10nId - The fluent ID for the button's text
+ * @property {boolean} selected - Whether or not the button is currently selected.
+ * @slot [default] - Used to append the l10n string to the button.
+ */
+export class MozPageNavButton extends MozLitElement {
+ static properties = {
+ iconSrc: { type: String },
+ l10nId: { type: String },
+ selected: { type: Boolean },
+ };
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.setAttribute("role", "none");
+ }
+
+ static queries = {
+ buttonEl: "button",
+ };
+
+ get view() {
+ return this.getAttribute("view");
+ }
+
+ activate() {
+ this.dispatchEvent(
+ new CustomEvent("change-view", {
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }
+
+ render() {
+ return html`
+ <link
+ rel="stylesheet"
+ href="chrome://global/content/elements/moz-page-nav-button.css"
+ />
+ <button
+ aria-selected=${this.selected}
+ tabindex=${this.selected ? 0 : -1}
+ role="tab"
+ ?selected=${this.selected}
+ @click=${this.activate}
+ >
+ <img class="page-nav-icon" src=${this.iconSrc} />
+ <slot></slot>
+ </button>
+ `;
+ }
+}
+customElements.define("moz-page-nav-button", MozPageNavButton);
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs b/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs
new file mode 100644
index 0000000000..4ac7b455cf
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html } from "../vendor/lit.all.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "./moz-page-nav.mjs";
+
+export default {
+ title: "UI Widgets/Page Nav",
+ component: "moz-page-nav",
+ parameters: {
+ status: "in-development",
+ actions: {
+ handles: ["change-view"],
+ },
+ fluent: `
+moz-page-nav-button-one = View 1
+ .title = View 1
+moz-page-nav-button-two = View 2
+ .title = View 2
+moz-page-nav-button-three = View 3
+ .title = View 3
+moz-page-link-one = Support Page
+ .title = Support Page
+moz-page-nav-heading =
+ .heading = Heading
+ `,
+ },
+};
+
+const Template = () => html`
+ <style>
+ #page {
+ height: 100%;
+ display: flex;
+
+ @media (max-width: 52rem) {
+ grid-template-columns: 82px 1fr;
+ }
+ }
+ moz-page-nav {
+ margin-inline-start: 10px;
+ --page-nav-margin-top: 10px;
+
+ @media (max-width: 52rem) {
+ margin-inline-start: 0;
+ }
+ }
+ </style>
+ <div id="page">
+ <moz-page-nav data-l10n-id="moz-page-nav-heading" data-l10n-attrs="heading">
+ <moz-page-nav-button
+ view="view-one"
+ data-l10n-id="moz-page-nav-button-one"
+ iconSrc="chrome://browser/skin/preferences/category-general.svg"
+ >
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="view-two"
+ data-l10n-id="moz-page-nav-button-two"
+ iconSrc="chrome://browser/skin/preferences/category-general.svg"
+ >
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="view-three"
+ data-l10n-id="moz-page-nav-button-three"
+ iconSrc="chrome://browser/skin/preferences/category-general.svg"
+ >
+ </moz-page-nav-button>
+ </moz-page-nav>
+ <main></main>
+ </div>
+`;
+
+export const Default = Template.bind({});
+Default.args = {};
diff --git a/toolkit/content/widgets/moz-support-link/moz-support-link.mjs b/toolkit/content/widgets/moz-support-link/moz-support-link.mjs
index 23f18ac434..9d2d6ffac2 100644
--- a/toolkit/content/widgets/moz-support-link/moz-support-link.mjs
+++ b/toolkit/content/widgets/moz-support-link/moz-support-link.mjs
@@ -82,7 +82,7 @@ export default class MozSupportLink extends HTMLAnchorElement {
}
}
- attributeChangedCallback(attrName, oldVal, newVal) {
+ attributeChangedCallback(attrName) {
if (attrName === "support-page" || attrName === "utm-content") {
this.#setHref();
}
diff --git a/toolkit/content/widgets/moz-toggle/moz-toggle.css b/toolkit/content/widgets/moz-toggle/moz-toggle.css
index 8b67a81878..2005544181 100644
--- a/toolkit/content/widgets/moz-toggle/moz-toggle.css
+++ b/toolkit/content/widgets/moz-toggle/moz-toggle.css
@@ -38,11 +38,15 @@
--toggle-background-color-pressed-hover: var(--color-accent-primary-hover);
--toggle-background-color-pressed-active: var(--color-accent-primary-active);
--toggle-border-color: var(--border-interactive-color);
+ --toggle-border-color-hover: var(--toggle-border-color);
+ --toggle-border-color-active: var(--toggle-border-color);
--toggle-border-radius: var(--border-radius-circle);
--toggle-border-width: var(--border-width);
--toggle-height: var(--size-item-small);
--toggle-width: var(--size-item-large);
--toggle-dot-background-color: var(--toggle-border-color);
+ --toggle-dot-background-color-hover: var(--toggle-dot-background-color);
+ --toggle-dot-background-color-active: var(--toggle-dot-background-color);
--toggle-dot-background-color-on-pressed: var(--color-canvas);
--toggle-dot-margin: 1px;
--toggle-dot-height: calc(var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 * var(--toggle-border-width));
@@ -141,17 +145,6 @@
background-color: var(--toggle-background-color-disabled);
}
- .toggle-button {
- --toggle-dot-background-color: var(--color-accent-primary);
- --toggle-dot-background-color-hover: var(--color-accent-primary-hover);
- --toggle-dot-background-color-active: var(--color-accent-primary-active);
- --toggle-dot-background-color-on-pressed: var(--button-background-color);
- --toggle-background-color-disabled: var(--button-background-color-disabled);
- --toggle-border-color-hover: var(--border-interactive-color-hover);
- --toggle-border-color-active: var(--border-interactive-color-active);
- --toggle-border-color-disabled: var(--border-interactive-color-disabled);
- }
-
.toggle-button:enabled:hover {
border-color: var(--toggle-border-color-hover);
}
@@ -175,6 +168,24 @@
border-color: var(--toggle-dot-background-color-hover);
}
+ .toggle-button:hover::before,
+ .toggle-button:active::before {
+ background-color: var(--toggle-dot-background-color-hover);
+ }
+}
+
+@media (forced-colors) {
+ .toggle-button {
+ --toggle-dot-background-color: var(--color-accent-primary);
+ --toggle-dot-background-color-hover: var(--color-accent-primary-hover);
+ --toggle-dot-background-color-active: var(--color-accent-primary-active);
+ --toggle-dot-background-color-on-pressed: var(--button-background-color);
+ --toggle-background-color-disabled: var(--button-background-color-disabled);
+ --toggle-border-color-hover: var(--border-interactive-color-hover);
+ --toggle-border-color-active: var(--border-interactive-color-active);
+ --toggle-border-color-disabled: var(--border-interactive-color-disabled);
+ }
+
.toggle-button[aria-pressed="true"]:enabled::after {
border: 1px solid var(--button-background-color);
content: '';
@@ -189,10 +200,4 @@
.toggle-button[aria-pressed="true"]:enabled:active::after {
border-color: var(--toggle-border-color-active);
}
-
- .toggle-button:hover::before,
- .toggle-button:hover:active::before,
- .toggle-button:active::before {
- background-color: var(--toggle-dot-background-color-hover);
- }
}
diff --git a/toolkit/content/widgets/named-deck.js b/toolkit/content/widgets/named-deck.js
index 6b3b7a8835..42c96e278d 100644
--- a/toolkit/content/widgets/named-deck.js
+++ b/toolkit/content/widgets/named-deck.js
@@ -156,7 +156,7 @@
this.getRootNode().removeEventListener("keypress", this);
}
- attributeChangedCallback(name, oldVal, newVal) {
+ attributeChangedCallback(name) {
if (name == "orientation") {
if (this.isVertical) {
this.setAttribute("aria-orientation", this.orientation);
diff --git a/toolkit/content/widgets/notificationbox.js b/toolkit/content/widgets/notificationbox.js
index f23fb03a74..0161588853 100644
--- a/toolkit/content/widgets/notificationbox.js
+++ b/toolkit/content/widgets/notificationbox.js
@@ -622,7 +622,7 @@
async function createNotificationMessageElement() {
await window.ensureCustomElements("moz-message-bar");
- let MozMessageBar = customElements.get("moz-message-bar");
+ let MozMessageBar = await customElements.whenDefined("moz-message-bar");
class NotificationMessage extends MozMessageBar {
static queries = {
...MozMessageBar.queries,
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,
+};
diff --git a/toolkit/content/widgets/radio.js b/toolkit/content/widgets/radio.js
index 482323acb9..41e8a945ba 100644
--- a/toolkit/content/widgets/radio.js
+++ b/toolkit/content/widgets/radio.js
@@ -197,7 +197,7 @@
* @param {DOMNode} child
* The <radio> element that got removed
*/
- radioUnattached(child) {
+ radioUnattached() {
// Just invalidate the cache, next time it's fetched it'll get rebuilt.
this._radioChildren = null;
}
@@ -481,13 +481,13 @@
constructor() {
super();
- this.addEventListener("click", event => {
+ this.addEventListener("click", () => {
if (!this.disabled) {
this.control.selectedItem = this;
}
});
- this.addEventListener("mousedown", event => {
+ this.addEventListener("mousedown", () => {
if (!this.disabled) {
this.control.focusedItem = this;
}
diff --git a/toolkit/content/widgets/richlistbox.js b/toolkit/content/widgets/richlistbox.js
index 904ef9ceec..01d970e6ed 100644
--- a/toolkit/content/widgets/richlistbox.js
+++ b/toolkit/content/widgets/richlistbox.js
@@ -126,7 +126,7 @@
}
});
- this.addEventListener("focus", event => {
+ this.addEventListener("focus", () => {
if (this.getRowCount() > 0) {
if (this.currentIndex == -1) {
this.currentIndex = this.getIndexOfFirstVisibleRow();
diff --git a/toolkit/content/widgets/search-textbox.js b/toolkit/content/widgets/search-textbox.js
index abdcfa2999..b254b2796d 100644
--- a/toolkit/content/widgets/search-textbox.js
+++ b/toolkit/content/widgets/search-textbox.js
@@ -214,7 +214,7 @@
}
}
- on_mousedown(event) {
+ on_mousedown() {
if (!this.hasAttribute("focused")) {
this.setSelectionRange(0, 0);
this.focus();
diff --git a/toolkit/content/widgets/tabbox.js b/toolkit/content/widgets/tabbox.js
index 997e8413f2..b1b2ddecce 100644
--- a/toolkit/content/widgets/tabbox.js
+++ b/toolkit/content/widgets/tabbox.js
@@ -24,15 +24,15 @@
}
connectedCallback() {
- Services.els.addSystemEventListener(document, "keydown", this, false);
+ document.addEventListener("keydown", this, { mozSystemGroup: true });
window.addEventListener("unload", this.disconnectedCallback, {
once: true,
});
}
disconnectedCallback() {
+ document.removeEventListener("keydown", this, { mozSystemGroup: true });
window.removeEventListener("unload", this.disconnectedCallback);
- Services.els.removeSystemEventListener(document, "keydown", this, false);
}
set handleCtrlTab(val) {
@@ -729,7 +729,7 @@
direction = 1,
wrap = false,
startWithAdjacent = true,
- filter = tab => true,
+ filter = () => true,
} = opts;
let tab = startTab;
@@ -804,7 +804,7 @@
}
}
- _canAdvanceToTab(aTab) {
+ _canAdvanceToTab() {
return true;
}
diff --git a/toolkit/content/widgets/text.js b/toolkit/content/widgets/text.js
index ca10f1489e..7bbf6db4cc 100644
--- a/toolkit/content/widgets/text.js
+++ b/toolkit/content/widgets/text.js
@@ -67,7 +67,7 @@
this.formatAccessKey();
}
- _onClick(event) {
+ _onClick() {
let controlElement = this.labeledControlElement;
if (!controlElement || this.disabled) {
return;
diff --git a/toolkit/content/widgets/textrecognition.js b/toolkit/content/widgets/textrecognition.js
index 887d576770..c517f7bfb1 100644
--- a/toolkit/content/widgets/textrecognition.js
+++ b/toolkit/content/widgets/textrecognition.js
@@ -4,7 +4,7 @@
"use strict";
// This is a UA widget. It runs in per-origin UA widget scope,
-// to be loaded by UAWidgetsChild.jsm.
+// to be loaded by UAWidgetsChild.sys.mjs.
this.TextRecognitionWidget = class {
/**
diff --git a/toolkit/content/widgets/tree.js b/toolkit/content/widgets/tree.js
index 322e42586e..4993bef0c2 100644
--- a/toolkit/content/widgets/tree.js
+++ b/toolkit/content/widgets/tree.js
@@ -515,7 +515,7 @@
}
}
- _onDragMouseUp(aEvent) {
+ _onDragMouseUp() {
var col = document.treecolDragging;
if (!col) {
return;
@@ -786,7 +786,7 @@
}
});
- this.addEventListener("touchend", event => {
+ this.addEventListener("touchend", () => {
this._touchY = -1;
});
@@ -840,7 +840,7 @@
}
});
- this.addEventListener("focus", event => {
+ this.addEventListener("focus", () => {
this.focused = true;
if (this.currentIndex == -1 && this.view.rowCount > 0) {
this.currentIndex = this.getFirstVisibleRow();
@@ -1651,7 +1651,7 @@
this.ensureRowIsVisible(edge);
}
- _handleEnter(event) {
+ _handleEnter() {
if (this._editingColumn) {
this.stopEditing(true);
this.focus();
diff --git a/toolkit/content/widgets/videocontrols.js b/toolkit/content/widgets/videocontrols.js
index 21c8946e60..73a32164aa 100644
--- a/toolkit/content/widgets/videocontrols.js
+++ b/toolkit/content/widgets/videocontrols.js
@@ -5,7 +5,7 @@
"use strict";
// This is a UA widget. It runs in per-origin UA widget scope,
-// to be loaded by UAWidgetsChild.jsm.
+// to be loaded by UAWidgetsChild.sys.mjs.
/*
* This is the class of entry. It will construct the actual implementation
@@ -64,11 +64,8 @@ this.VideoControlsWidget = class {
// the underlying element state hasn't changed in ways that we
// care about. This can happen if the property is set again
// without a value change.
- if (
- this.impl &&
- this.impl.constructor == newImpl &&
- this.impl.elementStateMatches(this.element)
- ) {
+ if (this.impl && this.impl.constructor == newImpl) {
+ this.impl.onchange();
return;
}
if (this.impl) {
@@ -458,10 +455,10 @@ this.VideoControlsImplWidget = class {
this.statusIcon.setAttribute("type", "error");
this.updateErrorText();
this.setupStatusFader(true);
- } else if (VideoControlsWidget.isPictureInPictureVideo(this.video)) {
- this.setShowPictureInPictureMessage(true);
}
+ this.updatePictureInPictureMessage();
+
if (this.video.readyState >= this.video.HAVE_METADATA) {
// According to the spec[1], at the HAVE_METADATA (or later) state, we know
// the video duration and dimensions, which means we can calculate whether or
@@ -934,6 +931,8 @@ this.VideoControlsImplWidget = class {
// Since this event come from the layout, this is the only place
// we are sure of that probing into layout won't trigger or force
// reflow.
+ // FIXME(emilio): We should rewrite this to just use
+ // ResizeObserver, probably.
this.reflowTriggeringCallValidator.isReflowTriggeringPropsAllowed = true;
this.updateReflowedDimensions();
this.reflowTriggeringCallValidator.isReflowTriggeringPropsAllowed = false;
@@ -1095,7 +1094,10 @@ this.VideoControlsImplWidget = class {
);
},
- setShowPictureInPictureMessage(showMessage) {
+ updatePictureInPictureMessage() {
+ let showMessage =
+ !this.hasError() &&
+ VideoControlsWidget.isPictureInPictureVideo(this.video);
this.pictureInPictureOverlay.hidden = !showMessage;
this.isShowingPictureInPictureMessage = showMessage;
},
@@ -1188,7 +1190,7 @@ this.VideoControlsImplWidget = class {
}
},
- onScrubberInput(e) {
+ onScrubberInput() {
const duration = Math.round(this.video.duration * 1000); // in ms
let time = this.scrubber.value;
@@ -1200,7 +1202,7 @@ this.VideoControlsImplWidget = class {
this.pauseVideoDuringDragging();
},
- onScrubberChange(e) {
+ onScrubberChange() {
this.scrubber.isDragging = false;
if (this.isPausedByDragging) {
@@ -1815,12 +1817,7 @@ this.VideoControlsImplWidget = class {
updateMuteButtonState() {
var muted = this.isEffectivelyMuted;
-
- if (muted) {
- this.muteButton.setAttribute("muted", "true");
- } else {
- this.muteButton.removeAttribute("muted");
- }
+ this.muteButton.toggleAttribute("muted", muted);
var id = muted
? "videocontrols-unmute-button"
@@ -2026,12 +2023,7 @@ this.VideoControlsImplWidget = class {
},
setCastingButtonState() {
- if (this.isCastingOn) {
- this.castingButton.setAttribute("enabled", "true");
- } else {
- this.castingButton.removeAttribute("enabled");
- }
-
+ this.castingButton.toggleAttribute("enabled", this.isCastingOn);
this.adjustControlSize();
},
@@ -2058,22 +2050,15 @@ this.VideoControlsImplWidget = class {
},
setClosedCaptionButtonState() {
- if (this.isClosedCaptionOn) {
- this.closedCaptionButton.setAttribute("enabled", "true");
- } else {
- this.closedCaptionButton.removeAttribute("enabled");
- }
-
+ this.closedCaptionButton.toggleAttribute(
+ "enabled",
+ this.isClosedCaptionOn
+ );
let ttItems = this.textTrackList.childNodes;
for (let tti of ttItems) {
const idx = +tti.getAttribute("index");
-
- if (idx == this.currentTextTrackIndex) {
- tti.setAttribute("aria-checked", "true");
- } else {
- tti.setAttribute("aria-checked", "false");
- }
+ tti.setAttribute("aria-checked", idx == this.currentTextTrackIndex);
}
this.adjustControlSize();
@@ -2804,10 +2789,6 @@ this.VideoControlsImplWidget = class {
if (this.Utils.isTouchControls) {
this.TouchUtils.init(this.shadowRoot, this.Utils);
}
- this.shadowRoot.firstChild.dispatchEvent(
- new this.window.CustomEvent("VideoBindingAttached")
- );
-
this._setupEventListeners();
}
@@ -2920,9 +2901,9 @@ this.VideoControlsImplWidget = class {
this.l10n.translateRoots();
}
- elementStateMatches(element) {
- let elementInPiP = VideoControlsWidget.isPictureInPictureVideo(element);
- return this.isShowingPictureInPictureMessage == elementInPiP;
+ onchange() {
+ this.Utils.updatePictureInPictureMessage();
+ this.shadowRoot.firstChild.removeAttribute("flipped");
}
teardown() {
@@ -3080,14 +3061,9 @@ this.NoControlsMobileImplWidget = class {
},
};
this.Utils.init(this.shadowRoot);
- this.Utils.video.dispatchEvent(
- new this.window.CustomEvent("MozNoControlsVideoBindingAttached")
- );
}
- elementStateMatches(element) {
- return true;
- }
+ onchange() {}
teardown() {
this.Utils.terminate();
@@ -3135,9 +3111,7 @@ this.NoControlsPictureInPictureImplWidget = class {
this.shadowRoot.firstElementChild.setAttribute("localedir", direction);
}
- elementStateMatches(element) {
- return true;
- }
+ onchange() {}
teardown() {}
@@ -3312,9 +3286,7 @@ this.NoControlsDesktopImplWidget = class {
this.Utils.init(this.shadowRoot, this.prefs);
}
- elementStateMatches(element) {
- return true;
- }
+ onchange() {}
teardown() {
this.Utils.terminate();
diff --git a/toolkit/content/widgets/wizard.js b/toolkit/content/widgets/wizard.js
index 6eb4bcb517..c4285fada5 100644
--- a/toolkit/content/widgets/wizard.js
+++ b/toolkit/content/widgets/wizard.js
@@ -359,7 +359,7 @@
this._wizardButtons.onPageChange();
}
- _advanceFocusToPage(aPage) {
+ _advanceFocusToPage() {
if (!this._hasLoaded) {
return;
}