summaryrefslogtreecommitdiffstats
path: root/toolkit/content/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/widgets')
-rw-r--r--toolkit/content/widgets/autocomplete-popup.js71
-rw-r--r--toolkit/content/widgets/autocomplete-richlistitem.js152
-rw-r--r--toolkit/content/widgets/button.js4
-rw-r--r--toolkit/content/widgets/infobar.css5
-rw-r--r--toolkit/content/widgets/mach_commands.py13
-rw-r--r--toolkit/content/widgets/marquee.css22
-rw-r--r--toolkit/content/widgets/marquee.js560
-rw-r--r--toolkit/content/widgets/menu.js2
-rw-r--r--toolkit/content/widgets/menulist.js14
-rw-r--r--toolkit/content/widgets/moz-button/moz-button.css3
-rw-r--r--toolkit/content/widgets/moz-button/moz-button.mjs5
-rw-r--r--toolkit/content/widgets/moz-card/moz-card.css56
-rw-r--r--toolkit/content/widgets/moz-card/moz-card.mjs20
-rw-r--r--toolkit/content/widgets/moz-card/moz-card.stories.mjs32
-rw-r--r--toolkit/content/widgets/moz-message-bar/moz-message-bar.css52
-rw-r--r--toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs16
-rw-r--r--toolkit/content/widgets/moz-page-nav/README.stories.md98
-rw-r--r--toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css35
-rw-r--r--toolkit/content/widgets/moz-page-nav/moz-page-nav.css3
-rw-r--r--toolkit/content/widgets/moz-toggle/moz-toggle.css24
-rw-r--r--toolkit/content/widgets/notificationbox.js4
-rw-r--r--toolkit/content/widgets/radio.js4
-rw-r--r--toolkit/content/widgets/richlistbox.js24
-rw-r--r--toolkit/content/widgets/tabbox.js4
-rw-r--r--toolkit/content/widgets/tree.js2
-rw-r--r--toolkit/content/widgets/wizard.js4
26 files changed, 720 insertions, 509 deletions
diff --git a/toolkit/content/widgets/autocomplete-popup.js b/toolkit/content/widgets/autocomplete-popup.js
index a13ba1bc62..8bbd012f31 100644
--- a/toolkit/content/widgets/autocomplete-popup.js
+++ b/toolkit/content/widgets/autocomplete-popup.js
@@ -211,24 +211,32 @@
return -1;
}
- var newIdx = aIndex + (aReverse ? -1 : 1) * aAmount;
- if (
- (aReverse && aIndex == -1) ||
- (newIdx > aMaxRow && aIndex != aMaxRow)
- ) {
- newIdx = aMaxRow;
- } else if ((!aReverse && aIndex == -1) || (newIdx < 0 && aIndex != 0)) {
- newIdx = 0;
- }
+ do {
+ var newIdx = aIndex + (aReverse ? -1 : 1) * aAmount;
+ if (
+ (aReverse && aIndex == -1) ||
+ (newIdx > aMaxRow && aIndex != aMaxRow)
+ ) {
+ newIdx = aMaxRow;
+ } else if ((!aReverse && aIndex == -1) || (newIdx < 0 && aIndex != 0)) {
+ newIdx = 0;
+ }
- if (
- (newIdx < 0 && aIndex == 0) ||
- (newIdx > aMaxRow && aIndex == aMaxRow)
- ) {
- aIndex = -1;
- } else {
- aIndex = newIdx;
- }
+ if (
+ (newIdx < 0 && aIndex == 0) ||
+ (newIdx > aMaxRow && aIndex == aMaxRow)
+ ) {
+ aIndex = -1;
+ } else {
+ aIndex = newIdx;
+ }
+
+ if (aIndex == -1) {
+ return -1;
+ }
+ } while (
+ !this.richlistbox.canUserSelect(this.richlistbox.getItemAtIndex(aIndex))
+ );
return aIndex;
}
@@ -313,12 +321,7 @@
_collapseUnusedItems() {
let existingItemsCount = this.richlistbox.children.length;
for (let i = this.matchCount; i < existingItemsCount; ++i) {
- let item = this.richlistbox.children[i];
-
- item.collapsed = true;
- if (typeof item._onCollapse == "function") {
- item._onCollapse();
- }
+ this.richlistbox.children[i].collapsed = true;
}
}
@@ -408,10 +411,9 @@
// The styles on the list which have different <content> structure and overrided
// _adjustAcItem() are unreusable.
const UNREUSEABLE_STYLES = [
- "autofill-profile",
- "autofill-footer",
- "autofill-clear-button",
- "autofill-insecureWarning",
+ "autofill",
+ "action",
+ "status",
"generatedPassword",
"generic",
"importableLearnMore",
@@ -434,17 +436,14 @@
if (!reusable) {
let options = null;
switch (style) {
- case "autofill-profile":
- options = { is: "autocomplete-profile-listitem" };
- break;
- case "autofill-footer":
- options = { is: "autocomplete-profile-listitem-footer" };
+ case "autofill":
+ options = { is: "autocomplete-autofill-richlistitem" };
break;
- case "autofill-clear-button":
- options = { is: "autocomplete-profile-listitem-clear-button" };
+ case "action":
+ options = { is: "autocomplete-action-richlistitem" };
break;
- case "autofill-insecureWarning":
- options = { is: "autocomplete-creditcard-insecure-field" };
+ case "status":
+ options = { is: "autocomplete-status-richlistitem" };
break;
case "generic":
options = { is: "autocomplete-two-line-richlistitem" };
diff --git a/toolkit/content/widgets/autocomplete-richlistitem.js b/toolkit/content/widgets/autocomplete-richlistitem.js
index fddd5b4029..b339ab1e27 100644
--- a/toolkit/content/widgets/autocomplete-richlistitem.js
+++ b/toolkit/content/widgets/autocomplete-richlistitem.js
@@ -522,6 +522,11 @@
return;
}
+ let label = this.getAttribute("ac-label");
+ if (label && JSON.parse(label)?.noLearnMore) {
+ return;
+ }
+
let baseURL = Services.urlFormatter.formatURLPref(
"app.support.baseURL"
);
@@ -715,6 +720,129 @@
}
}
+ // This type has an action that is triggered when activated. The comment
+ // for that result should contain a fillMessageName -- the message to send --
+ // and, optionally a secondary label, for example:
+ // { "fillMessageName": "Fill:Clear", secondary: "Second Label" }
+ class MozAutocompleteActionRichlistitem extends MozAutocompleteTwoLineRichlistitem {
+ constructor() {
+ super();
+ this.selectedByMouseOver = true;
+ }
+
+ _adjustAcItem() {
+ super._adjustAcItem();
+
+ let comment = JSON.parse(this.getAttribute("ac-label"));
+ this.querySelector(".line2-label").textContent = comment?.secondary || "";
+ this.querySelector(".ac-site-icon").collapsed =
+ this.getAttribute("ac-image") == "";
+ }
+ }
+
+ // A row that conveys status information assigned from the status field
+ // within the comment associated with the selected item in the list.
+ class MozAutocompleteStatusRichlistitem extends MozAutocompleteTwoLineRichlistitem {
+ static get markup() {
+ return `<div class="ac-status" xmlns="http://www.w3.org/1999/xhtml"></div>`;
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.parentNode.addEventListener("select", this);
+ this.eventListenerParentNode = this.parentNode;
+ }
+
+ disconnectedCallback() {
+ this.eventListenerParentNode?.removeEventListener("select", this);
+ this.eventListenerParentNode = null;
+ }
+
+ handleEvent(event) {
+ if (event.type == "select") {
+ let selectedItem = event.target.selectedItem;
+ if (selectedItem) {
+ this.#setStatus(selectedItem);
+ }
+ }
+ }
+
+ #setStatus(item) {
+ // For normal rows, use that row's comment, otherwise use the status's
+ // comment which serves as the default label.
+ let target =
+ !item || item instanceof MozAutocompleteActionRichlistitem
+ ? this
+ : item;
+
+ let comment = JSON.parse(target.getAttribute("ac-comment"));
+ let statusBox = this.querySelector(".ac-status");
+ statusBox.textContent = comment?.status || "";
+ }
+
+ _adjustAcItem() {
+ super._adjustAcItem();
+ this.#setStatus(this);
+ this.setAttribute("disabled", "true");
+ }
+ }
+
+ class MozAutocompleteAutoFillRichlistitem extends MozAutocompleteTwoLineRichlistitem {
+ constructor() {
+ super();
+ this.selectedByMouseOver = true;
+ }
+
+ _adjustAcItem() {
+ let { primary, secondary, ariaLabel } = JSON.parse(
+ this.getAttribute("ac-value")
+ );
+
+ let line1Label = this.querySelector(".line1-label");
+ line1Label.textContent = primary.toString();
+
+ let line2Label = this.querySelector(".line2-label");
+ line2Label.textContent = secondary.toString();
+
+ if (ariaLabel) {
+ this.setAttribute("aria-label", ariaLabel);
+ }
+
+ this.querySelector(".ac-site-icon").collapsed =
+ this.getAttribute("ac-image") == "";
+ }
+
+ set selected(val) {
+ if (val) {
+ this.setAttribute("selected", "true");
+ } else {
+ this.removeAttribute("selected");
+ }
+
+ let { AutoCompleteParent } = ChromeUtils.importESModule(
+ "resource://gre/actors/AutoCompleteParent.sys.mjs"
+ );
+
+ let actor = AutoCompleteParent.getCurrentActor();
+ if (!actor) {
+ return;
+ }
+
+ let popup = actor.openedPopup;
+
+ setTimeout(() => {
+ let selectedIndex = popup ? popup.selectedIndex : -1;
+ actor.manager
+ .getActor("FormAutofill")
+ .sendAsyncMessage("FormAutofill:PreviewProfile", { selectedIndex });
+ }, 0);
+ }
+
+ get selected() {
+ return this.getAttribute("selected") == "true";
+ }
+ }
+
class MozAutocompleteGeneratedPasswordRichlistitem extends MozAutocompleteTwoLineRichlistitem {
constructor() {
super();
@@ -840,6 +968,14 @@
);
customElements.define(
+ "autocomplete-autofill-richlistitem",
+ MozAutocompleteAutoFillRichlistitem,
+ {
+ extends: "richlistitem",
+ }
+ );
+
+ customElements.define(
"autocomplete-login-richlistitem",
MozAutocompleteLoginRichlistitem,
{
@@ -848,6 +984,22 @@
);
customElements.define(
+ "autocomplete-action-richlistitem",
+ MozAutocompleteActionRichlistitem,
+ {
+ extends: "richlistitem",
+ }
+ );
+
+ customElements.define(
+ "autocomplete-status-richlistitem",
+ MozAutocompleteStatusRichlistitem,
+ {
+ extends: "richlistitem",
+ }
+ );
+
+ customElements.define(
"autocomplete-generated-password-richlistitem",
MozAutocompleteGeneratedPasswordRichlistitem,
{
diff --git a/toolkit/content/widgets/button.js b/toolkit/content/widgets/button.js
index ce48fac1e9..7ca2eddbed 100644
--- a/toolkit/content/widgets/button.js
+++ b/toolkit/content/widgets/button.js
@@ -84,7 +84,7 @@
).toLowerCase();
// If the accesskey of the current button is pressed, just activate it
- if (this.accessKey.toLowerCase() == charPressedLower) {
+ if (this.accessKey?.toLowerCase() == charPressedLower) {
this.click();
return;
}
@@ -201,7 +201,7 @@
while (iterator.nextNode()) {
var test = iterator.currentNode;
if (
- test.accessKey.toLowerCase() == aAccessKeyLower &&
+ test.accessKey?.toLowerCase() == aAccessKeyLower &&
!test.disabled &&
!test.collapsed &&
!test.hidden
diff --git a/toolkit/content/widgets/infobar.css b/toolkit/content/widgets/infobar.css
index ee811818b5..7818f1ef1d 100644
--- a/toolkit/content/widgets/infobar.css
+++ b/toolkit/content/widgets/infobar.css
@@ -32,11 +32,8 @@
}
.close {
- margin-block: 4px;
+ margin-block: 2px;
margin-inline-start: 8px;
- background-size: 12px;
- height: 24px;
- width: 24px;
align-self: flex-start;
}
}
diff --git a/toolkit/content/widgets/mach_commands.py b/toolkit/content/widgets/mach_commands.py
index 58a8b8fcda..79398ab11a 100644
--- a/toolkit/content/widgets/mach_commands.py
+++ b/toolkit/content/widgets/mach_commands.py
@@ -206,3 +206,16 @@ def addstory(command_context, name, project_name, path):
html_lit_import=html_lit_import,
)
)
+
+
+@Command(
+ "buildtokens",
+ category="misc",
+ description="Build the design tokens CSS files",
+)
+def buildtokens(command_context):
+ run_mach(
+ command_context,
+ "npm",
+ args=["run", "build", "--prefix=toolkit/themes/shared/design-system"],
+ )
diff --git a/toolkit/content/widgets/marquee.css b/toolkit/content/widgets/marquee.css
index b898cd0dce..6cdb52ca02 100644
--- a/toolkit/content/widgets/marquee.css
+++ b/toolkit/content/widgets/marquee.css
@@ -2,25 +2,19 @@
* 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/. */
-.outerDiv {
- overflow: hidden;
- width: -moz-available;
+slot {
+ display: block;
+ will-change: translate;
}
-.horizontal > .innerDiv {
- width: max-content;
- /* We want to create overflow of twice our available space. */
- padding: 0 100%;
-}
-
-/* disable scrolling in contenteditable */
-:host(:read-write) .innerDiv {
- padding: 0 !important;
+/* Disable the animation on contenteditable */
+:host(:read-write) > slot {
+ translate: none !important;
}
/* When printing or when the user doesn't want movement, we disable scrolling */
@media print, (prefers-reduced-motion) {
- .innerDiv {
- padding: 0 !important;
+ slot {
+ translate: none !important;
}
}
diff --git a/toolkit/content/widgets/marquee.js b/toolkit/content/widgets/marquee.js
index 8b18703b92..a694ffdca1 100644
--- a/toolkit/content/widgets/marquee.js
+++ b/toolkit/content/widgets/marquee.js
@@ -4,87 +4,25 @@
"use strict";
-/*
- * This is the class of entry. It will construct the actual implementation
- * according to the value of the "direction" property.
- */
this.MarqueeWidget = class {
constructor(shadowRoot) {
this.shadowRoot = shadowRoot;
this.element = shadowRoot.host;
- }
-
- /*
- * Callback called by UAWidgets right after constructor.
- */
- onsetup() {
- this.switchImpl();
- }
-
- /*
- * Callback called by UAWidgetsChild wheen the direction property
- * changes.
- */
- onchange() {
- this.switchImpl();
- }
-
- switchImpl() {
- let newImpl;
- switch (this.element.direction) {
- case "up":
- case "down":
- newImpl = MarqueeVerticalImplWidget;
- break;
- case "left":
- case "right":
- newImpl = MarqueeHorizontalImplWidget;
- break;
- }
-
- // Skip if we are asked to load the same implementation.
- // This can happen if the property is set again w/o value change.
- if (this.impl && this.impl.constructor == newImpl) {
- return;
- }
- this.teardown();
- if (newImpl) {
- this.impl = new newImpl(this.shadowRoot);
- this.impl.onsetup();
- }
- }
-
- teardown() {
- if (!this.impl) {
- return;
- }
- this.impl.teardown();
- this.shadowRoot.firstChild.remove();
- delete this.impl;
- }
-};
-
-this.MarqueeBaseImplWidget = class {
- constructor(shadowRoot) {
- this.shadowRoot = shadowRoot;
- this.element = shadowRoot.host;
this.document = this.element.ownerDocument;
this.window = this.document.defaultView;
+ // This needed for behavior=alternate, in order to know in which of the two
+ // directions we're going.
+ this.dirsign = 1;
+ this._currentLoop = this.element.loop;
+ this.animation = null;
+ this._restartScheduled = null;
}
onsetup() {
- this.generateContent();
-
- // Set up state.
- this._currentDirection = this.element.direction || "left";
- this._currentLoop = this.element.loop;
- this.dirsign = 1;
- this.startAt = 0;
- this.stopAt = 0;
- this.newPosition = 0;
- this.runId = 0;
- this.originalHeight = 0;
- this.invalidateCache = true;
+ // White-space isn't allowed because a marquee could be
+ // inside 'white-space: pre'
+ this.shadowRoot.innerHTML = `<link rel="stylesheet" href="chrome://global/content/elements/marquee.css"
+ /><slot></slot>`;
this._mutationObserver = new this.window.MutationObserver(aMutations =>
this._mutationActor(aMutations)
@@ -92,7 +30,7 @@ this.MarqueeBaseImplWidget = class {
this._mutationObserver.observe(this.element, {
attributes: true,
attributeOldValue: true,
- attributeFilter: ["loop", "", "behavior", "direction", "width", "height"],
+ attributeFilter: ["loop", "direction", "behavior"],
});
// init needs to be run after the page has loaded in order to calculate
@@ -108,12 +46,13 @@ this.MarqueeBaseImplWidget = class {
}
teardown() {
+ this.doStop();
this._mutationObserver.disconnect();
- this.window.clearTimeout(this.runId);
this.window.removeEventListener("load", this);
this.shadowRoot.removeEventListener("marquee-start", this);
this.shadowRoot.removeEventListener("marquee-stop", this);
+ this.shadowRoot.replaceChildren();
}
handleEvent(aEvent) {
@@ -131,15 +70,26 @@ this.MarqueeBaseImplWidget = class {
case "marquee-stop":
this.doStop();
break;
+ case "finish":
+ this._animationFinished();
+ break;
}
}
- get outerDiv() {
- return this.shadowRoot.firstChild;
- }
-
- get innerDiv() {
- return this.shadowRoot.getElementById("innerDiv");
+ _animationFinished() {
+ let behavior = this.element.behavior;
+ let shouldLoop =
+ this._currentLoop > 1 || (this._currentLoop == -1 && behavior != "slide");
+ if (shouldLoop) {
+ if (this._currentLoop > 0) {
+ this._currentLoop--;
+ }
+ if (behavior == "alternate") {
+ this.dirsign = -this.dirsign;
+ }
+ this.doStop();
+ this.doStart();
+ }
}
get scrollDelayWithTruespeed() {
@@ -149,269 +99,249 @@ this.MarqueeBaseImplWidget = class {
return this.element.scrollDelay;
}
- doStart() {
- if (this.runId == 0) {
- var lambda = () => this._doMove(false);
- this.runId = this.window.setTimeout(
- lambda,
- this.scrollDelayWithTruespeed - this._deltaStartStop
- );
- this._deltaStartStop = 0;
- }
- }
-
- doStop() {
- if (this.runId != 0) {
- this._deltaStartStop = Date.now() - this._lastMoveDate;
- this.window.clearTimeout(this.runId);
- }
-
- this.runId = 0;
+ get slot() {
+ return this.shadowRoot.lastChild;
}
- _fireEvent(aName, aBubbles, aCancelable) {
- var e = this.document.createEvent("Events");
- e.initEvent(aName, aBubbles, aCancelable);
- this.element.dispatchEvent(e);
+ /**
+ * Computes CSS-derived values needed to compute the transform of the
+ * contents.
+ *
+ * In particular, it measures the auto width and height of the contents,
+ * and the effective width and height of the marquee itself, along with its
+ * css directionality (which affects the effective direction).
+ */
+ getMetrics() {
+ let slot = this.slot;
+ slot.style.width = "max-content";
+ let slotCS = this.window.getComputedStyle(slot);
+ let marqueeCS = this.window.getComputedStyle(this.element);
+ let contentWidth = parseFloat(slotCS.width) || 0;
+ let contentHeight = parseFloat(slotCS.height) || 0;
+ let marqueeWidth = parseFloat(marqueeCS.width) || 0;
+ let marqueeHeight = parseFloat(marqueeCS.height) || 0;
+ slot.style.width = "";
+ return {
+ contentWidth,
+ contentHeight,
+ marqueeWidth,
+ marqueeHeight,
+ cssDirection: marqueeCS.direction,
+ };
}
- _doMove(aResetPosition) {
- this._lastMoveDate = Date.now();
-
- // invalidateCache is true at first load and whenever an attribute
- // is changed
- if (this.invalidateCache) {
- this.invalidateCache = false; // we only want this to run once every scroll direction change
-
- var corrvalue = 0;
-
- switch (this._currentDirection) {
- case "up":
- case "down": {
- let height = this.window.getComputedStyle(this.element).height;
- this.outerDiv.style.height = height;
- if (this.originalHeight > this.outerDiv.offsetHeight) {
- corrvalue = this.originalHeight - this.outerDiv.offsetHeight;
+ /**
+ * Gets the layout metrics from getMetrics(), and returns an object
+ * describing the start, end, and axis of the animation for the given marquee
+ * behavior and direction.
+ */
+ getTransformParameters({
+ contentWidth,
+ contentHeight,
+ marqueeWidth,
+ marqueeHeight,
+ cssDirection,
+ }) {
+ const innerWidth = marqueeWidth - contentWidth;
+ const innerHeight = marqueeHeight - contentHeight;
+ const dir = this.element.direction;
+
+ let start = 0;
+ let end = 0;
+ const axis = dir == "up" || dir == "down" ? "y" : "x";
+ switch (this.element.behavior) {
+ case "alternate":
+ switch (dir) {
+ case "up":
+ case "down": {
+ if (innerHeight >= 0) {
+ start = innerHeight;
+ end = 0;
+ } else {
+ start = 0;
+ end = innerHeight;
+ }
+ if (dir == "down") {
+ [start, end] = [end, start];
+ }
+ if (this.dirsign == -1) {
+ [start, end] = [end, start];
+ }
+ break;
}
- this.innerDiv.style.padding = height + " 0";
- let isUp = this._currentDirection == "up";
- if (isUp) {
- this.dirsign = 1;
- this.startAt =
- this.element.behavior == "alternate"
- ? this.originalHeight - corrvalue
- : 0;
- this.stopAt =
- this.element.behavior == "alternate" ||
- this.element.behavior == "slide"
- ? parseInt(height) + corrvalue
- : this.originalHeight + parseInt(height);
- } else {
- this.dirsign = -1;
- this.startAt =
- this.element.behavior == "alternate"
- ? parseInt(height) + corrvalue
- : this.originalHeight + parseInt(height);
- this.stopAt =
- this.element.behavior == "alternate" ||
- this.element.behavior == "slide"
- ? this.originalHeight - corrvalue
- : 0;
+ case "right":
+ case "left":
+ default: {
+ if (innerWidth >= 0) {
+ start = innerWidth;
+ end = 0;
+ } else {
+ start = 0;
+ end = innerWidth;
+ }
+ if (dir == "right") {
+ [start, end] = [end, start];
+ }
+ if (cssDirection == "rtl") {
+ [start, end] = [end, start];
+ }
+ if (this.dirsign == -1) {
+ [start, end] = [end, start];
+ }
+ break;
}
- break;
}
- case "left":
- case "right":
- default: {
- let isRight = this._currentDirection == "right";
- // NOTE: It's important to use getComputedStyle() to not account for the padding.
- let innerWidth = parseInt(
- this.window.getComputedStyle(this.innerDiv).width
- );
- if (innerWidth > this.outerDiv.offsetWidth) {
- corrvalue = innerWidth - this.outerDiv.offsetWidth;
+ break;
+ case "slide":
+ switch (dir) {
+ case "up": {
+ start = marqueeHeight;
+ end = 0;
+ break;
}
- let rtl =
- this.window.getComputedStyle(this.element).direction == "rtl";
- if (isRight != rtl) {
- this.dirsign = -1;
- this.stopAt =
- this.element.behavior == "alternate" ||
- this.element.behavior == "slide"
- ? innerWidth - corrvalue
- : 0;
- this.startAt =
- this.outerDiv.offsetWidth +
- (this.element.behavior == "alternate"
- ? corrvalue
- : innerWidth + this.stopAt);
- } else {
- this.dirsign = 1;
- this.startAt =
- this.element.behavior == "alternate" ? innerWidth - corrvalue : 0;
- this.stopAt =
- this.outerDiv.offsetWidth +
- (this.element.behavior == "alternate" ||
- this.element.behavior == "slide"
- ? corrvalue
- : innerWidth + this.startAt);
+ case "down": {
+ start = -contentHeight;
+ end = innerHeight;
+ break;
}
- if (rtl) {
- this.startAt = -this.startAt;
- this.stopAt = -this.stopAt;
- this.dirsign = -this.dirsign;
+ case "right":
+ default: {
+ let isRight = dir == "right";
+ if (cssDirection == "rtl") {
+ isRight = !isRight;
+ }
+ if (isRight) {
+ start = -contentWidth;
+ end = innerWidth;
+ } else {
+ start = marqueeWidth;
+ end = 0;
+ }
+ break;
}
- break;
}
- }
-
- if (aResetPosition) {
- this.newPosition = this.startAt;
- this._fireEvent("start", false, false);
- }
- } // end if
-
- this.newPosition =
- this.newPosition + this.dirsign * this.element.scrollAmount;
-
- if (
- (this.dirsign == 1 && this.newPosition > this.stopAt) ||
- (this.dirsign == -1 && this.newPosition < this.stopAt)
- ) {
- switch (this.element.behavior) {
- case "alternate":
- // lets start afresh
- this.invalidateCache = true;
-
- // swap direction
- const swap = { left: "right", down: "up", up: "down", right: "left" };
- this._currentDirection = swap[this._currentDirection] || "left";
- this.newPosition = this.stopAt;
-
- if (
- this._currentDirection == "up" ||
- this._currentDirection == "down"
- ) {
- this.outerDiv.scrollTop = this.newPosition;
- } else {
- this.outerDiv.scrollLeft = this.newPosition;
- }
-
- if (this._currentLoop != 1) {
- this._fireEvent("bounce", false, true);
- }
- break;
-
- case "slide":
- if (this._currentLoop > 1) {
- this.newPosition = this.startAt;
+ break;
+ case "scroll":
+ default:
+ switch (dir) {
+ case "up":
+ case "down": {
+ start = marqueeHeight;
+ end = -contentHeight;
+ if (dir == "down") {
+ [start, end] = [end, start];
+ }
+ break;
}
- break;
-
- default:
- this.newPosition = this.startAt;
-
- if (
- this._currentDirection == "up" ||
- this._currentDirection == "down"
- ) {
- this.outerDiv.scrollTop = this.newPosition;
- } else {
- this.outerDiv.scrollLeft = this.newPosition;
+ case "right":
+ case "left":
+ default: {
+ start = marqueeWidth;
+ end = -contentWidth;
+ if (dir == "right") {
+ [start, end] = [end, start];
+ }
+ if (cssDirection == "rtl") {
+ [start, end] = [end, start];
+ }
+ break;
}
+ }
+ break;
+ }
+ return { start, end, axis };
+ }
- // dispatch start event, even when this._currentLoop == 1, comp. with IE6
- this._fireEvent("start", false, false);
+ /**
+ * Measures the marquee contents, and starts the marquee animation if needed.
+ * The translate animation is applied to the <slot> element.
+ * Bouncing and looping is implemented in the finish event handler for the
+ * given animation (see _animationFinished()).
+ */
+ doStart() {
+ if (this.animation) {
+ return;
+ }
+ let scrollAmount = this.element.scrollAmount;
+ if (!scrollAmount) {
+ return;
+ }
+ let metrics = this.getMetrics();
+ let { axis, start, end } = this.getTransformParameters(metrics);
+ let duration =
+ (Math.abs(end - start) * this.scrollDelayWithTruespeed) / scrollAmount;
+ let startValue = start + "px";
+ let endValue = end + "px";
+ if (axis == "y") {
+ startValue = "0 " + startValue;
+ endValue = "0 " + endValue;
+ }
+ // NOTE(emilio): It seems tempting to use `iterations` here, but doing so
+ // wouldn't be great because this uses current layout values (via
+ // getMetrics()), so sizes wouldn't update. This way we update once per
+ // animation iteration.
+ //
+ // fill: forwards is needed so that behavior=slide doesn't jump back to the
+ // start after the animation finishes.
+ this.animation = this.slot.animate(
+ {
+ translate: [startValue, endValue],
+ },
+ {
+ duration,
+ easing: "linear",
+ fill: "forwards",
}
+ );
+ this.animation.addEventListener("finish", this, { once: true });
+ }
- if (this._currentLoop > 1) {
- this._currentLoop--;
- } else if (this._currentLoop == 1) {
- if (
- this._currentDirection == "up" ||
- this._currentDirection == "down"
- ) {
- this.outerDiv.scrollTop = this.stopAt;
- } else {
- this.outerDiv.scrollLeft = this.stopAt;
- }
- this.element.stop();
- this._fireEvent("finish", false, true);
- return;
- }
- } else if (
- this._currentDirection == "up" ||
- this._currentDirection == "down"
- ) {
- this.outerDiv.scrollTop = this.newPosition;
- } else {
- this.outerDiv.scrollLeft = this.newPosition;
+ doStop() {
+ if (!this.animation) {
+ return;
}
-
- var myThis = this;
- var lambda = function myTimeOutFunction() {
- myThis._doMove(false);
- };
- this.runId = this.window.setTimeout(lambda, this.scrollDelayWithTruespeed);
+ if (this._restartScheduled) {
+ this.window.cancelAnimationFrame(this._restartScheduled);
+ this._restartScheduled = null;
+ }
+ this.animation.removeEventListener("finish", this);
+ this.animation.cancel();
+ this.animation = null;
}
init() {
this.element.stop();
-
- if (this._currentDirection == "up" || this._currentDirection == "down") {
- // store the original height before we add padding
- this.innerDiv.style.padding = 0;
- this.originalHeight = this.innerDiv.offsetHeight;
- }
-
- this._doMove(true);
+ this.doStart();
}
_mutationActor(aMutations) {
while (aMutations.length) {
- var mutation = aMutations.shift();
- var attrName = mutation.attributeName.toLowerCase();
- var oldValue = mutation.oldValue;
- var target = mutation.target;
- var newValue = target.getAttribute(attrName);
-
- if (oldValue != newValue) {
- this.invalidateCache = true;
- switch (attrName) {
- case "loop":
- this._currentLoop = target.loop;
- break;
- case "direction":
- this._currentDirection = target.direction;
- break;
- }
+ let mutation = aMutations.shift();
+ let attrName = mutation.attributeName.toLowerCase();
+ let oldValue = mutation.oldValue;
+ let newValue = this.element.getAttribute(attrName);
+ if (oldValue == newValue) {
+ continue;
+ }
+ if (attrName == "loop") {
+ this._currentLoop = this.element.loop;
+ }
+ if (attrName == "direction" || attrName == "behavior") {
+ this._scheduleRestartIfNeeded();
}
}
}
-};
-
-this.MarqueeHorizontalImplWidget = class extends MarqueeBaseImplWidget {
- generateContent() {
- // White-space isn't allowed because a marquee could be
- // inside 'white-space: pre'
- this.shadowRoot.innerHTML = `<div class="outerDiv horizontal"
- ><link rel="stylesheet" href="chrome://global/content/elements/marquee.css"
- /><div class="innerDiv" id="innerDiv"
- ><slot
- /></div
- ></div>`;
- }
-};
-this.MarqueeVerticalImplWidget = class extends MarqueeBaseImplWidget {
- generateContent() {
- // White-space isn't allowed because a marquee could be
- // inside 'white-space: pre'
- this.shadowRoot.innerHTML = `<div class="outerDiv vertical"
- ><link rel="stylesheet" href="chrome://global/content/elements/marquee.css"
- /><div class="innerDiv" id="innerDiv"
- ><slot
- /></div
- ></div>`;
+ // Schedule a restart with the new parameters if we're running.
+ _scheduleRestartIfNeeded() {
+ if (!this.animation || this._restartScheduled != null) {
+ return;
+ }
+ this._restartScheduled = this.window.requestAnimationFrame(() => {
+ if (this.animation) {
+ this.doStop();
+ this.doStart();
+ }
+ });
}
};
diff --git a/toolkit/content/widgets/menu.js b/toolkit/content/widgets/menu.js
index 1a55d799b6..70b9521a27 100644
--- a/toolkit/content/widgets/menu.js
+++ b/toolkit/content/widgets/menu.js
@@ -19,7 +19,7 @@
this.setAttribute("value", val);
}
get value() {
- return this.getAttribute("value");
+ return this.getAttribute("value") || "";
}
// nsIDOMXULSelectControlItemElement
diff --git a/toolkit/content/widgets/menulist.js b/toolkit/content/widgets/menulist.js
index 3672d4ccf1..c7bff9c9d0 100644
--- a/toolkit/content/widgets/menulist.js
+++ b/toolkit/content/widgets/menulist.js
@@ -124,8 +124,6 @@
this.shadowRoot.appendChild(document.createElement("slot"));
}
- this.mSelectedInternal = null;
- this.mAttributeObserver = null;
this.setInitialSelection();
}
@@ -153,7 +151,7 @@
// nsIDOMXULSelectControlElement
get value() {
- return this.getAttribute("value");
+ return this.getAttribute("value") || "";
}
// nsIDOMXULMenuListElement
@@ -163,12 +161,12 @@
// nsIDOMXULMenuListElement
get image() {
- return this.getAttribute("image");
+ return this.getAttribute("image") || "";
}
// nsIDOMXULMenuListElement
get label() {
- return this.getAttribute("label");
+ return this.getAttribute("label") || "";
}
set description(val) {
@@ -176,7 +174,7 @@
}
get description() {
- return this.getAttribute("description");
+ return this.getAttribute("description") || "";
}
// nsIDOMXULMenuListElement
@@ -292,6 +290,10 @@
if (this.getAttribute("noinitialselection") === "true") {
return;
}
+
+ this.mSelectedInternal = null;
+ this.mAttributeObserver = null;
+
var popup = this.menupopup;
if (popup) {
var arr = popup.getElementsByAttribute("selected", "true");
diff --git a/toolkit/content/widgets/moz-button/moz-button.css b/toolkit/content/widgets/moz-button/moz-button.css
index 47567df41d..71d57ea93a 100644
--- a/toolkit/content/widgets/moz-button/moz-button.css
+++ b/toolkit/content/widgets/moz-button/moz-button.css
@@ -4,6 +4,8 @@
:host {
display: inline-block;
+ height: fit-content;
+ width: fit-content;
}
button {
@@ -133,6 +135,7 @@ button {
width: var(--button-size-icon);
height: var(--button-size-icon);
padding: var(--button-padding-icon);
+ color: var(--icon-color);
&[size=small] {
width: 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
index 3e7c151e61..a951dbf5d8 100644
--- a/toolkit/content/widgets/moz-button/moz-button.mjs
+++ b/toolkit/content/widgets/moz-button/moz-button.mjs
@@ -68,6 +68,11 @@ export default class MozButton extends MozLitElement {
}
}
+ // Delegate clicks on host to the button element.
+ click() {
+ this.buttonEl.click();
+ }
+
render() {
return html`
<link
diff --git a/toolkit/content/widgets/moz-card/moz-card.css b/toolkit/content/widgets/moz-card/moz-card.css
index 52c0ac0980..da5656e53e 100644
--- a/toolkit/content/widgets/moz-card/moz-card.css
+++ b/toolkit/content/widgets/moz-card/moz-card.css
@@ -7,7 +7,7 @@
--card-border-radius: var(--border-radius-medium);
--card-border-width: var(--border-width);
--card-border: var(--card-border-width) solid var(--card-border-color);
- --card-background-color: var(--box-background-color);
+ --card-background-color: var(--background-color-box);
--card-focus-outline: var(--focus-outline);
--card-box-shadow: var(--box-shadow-10);
/* Bug 1791816, 1839523: replace with spacing tokens */
@@ -17,9 +17,9 @@
/* Bug 1791816: replace with button tokens */
@media (prefers-contrast) {
- --button-border-color: var(--border-interactive-color);
- --button-border-color-hover: var(--border-interactive-color-hover);
- --button-border-color-active: var(--border-interactive-color-active);
+ --button-border-color: var(--border-color-interactive);
+ --button-border-color-hover: var(--border-color-interactive-hover);
+ --button-border-color-active: var(--border-color-interactive-active);
--card-border-color: color-mix(in srgb, currentColor 41%, transparent);
}
/* Bug 1791816: replace with button tokens */
@@ -27,9 +27,9 @@
--button-background-color: ButtonFace;
--button-background-color-hover: SelectedItemText;
--button-background-color-active: SelectedItemText;
- --button-border-color: var(--border-interactive-color);
- --button-border-color-hover: var(--border-interactive-color-hover);
- --button-border-color-active: var(--border-interactive-color-active);
+ --button-border-color: var(--border-color-interactive);
+ --button-border-color-hover: var(--border-color-interactive-hover);
+ --button-border-color-active: var(--border-color-interactive-active);
--button-text-color: ButtonText;
--button-text-color-hover: SelectedItem;
--button-text-color-active: SelectedItem;
@@ -81,6 +81,29 @@ summary {
gap: var(--card-gap);
padding-inline: var(--card-padding);
border-radius: var(--card-border-radius);
+
+ .chevron-icon {
+ background-image: url("chrome://global/skin/icons/arrow-down.svg");
+
+ details[open] & {
+ background-image: url("chrome://global/skin/icons/arrow-up.svg");
+ }
+ }
+
+ .chevron-icon,
+ #heading-icon {
+ background-position: center;
+ background-repeat: no-repeat;
+ -moz-context-properties: fill;
+ fill: currentColor;
+ width: 24px;
+ height: 24px;
+ min-width: 24px;
+ min-height: 24px;
+ padding: 0;
+ flex-shrink: 0;
+ align-self: flex-start;
+ }
}
#heading {
@@ -159,22 +182,3 @@ details {
outline: var(--card-focus-outline);
}
}
-
-.chevron-icon {
- background-image: url("chrome://global/skin/icons/arrow-down.svg");
- background-position: center;
- background-repeat: no-repeat;
- -moz-context-properties: fill;
- fill: currentColor;
- width: 24px;
- height: 24px;
- min-width: 24px;
- min-height: 24px;
- padding: 0;
- flex-shrink: 0;
- align-self: flex-start;
-
- details[open] & {
- background-image: url("chrome://global/skin/icons/arrow-up.svg");
- }
-}
diff --git a/toolkit/content/widgets/moz-card/moz-card.mjs b/toolkit/content/widgets/moz-card/moz-card.mjs
index 2bd6e0f987..5687608975 100644
--- a/toolkit/content/widgets/moz-card/moz-card.mjs
+++ b/toolkit/content/widgets/moz-card/moz-card.mjs
@@ -3,7 +3,11 @@
* 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 "chrome://global/content/vendor/lit.all.mjs";
+import {
+ html,
+ ifDefined,
+ when,
+} from "chrome://global/content/vendor/lit.all.mjs";
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
/**
@@ -18,6 +22,7 @@ import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
*
*
* @property {string} heading - The heading text that will be used for the card.
+ * @property {string} icon - (optional) A flag to indicate the header should include an icon
* @property {string} type - (optional) The type of card. No type specified
* will be the default card. The other available type is "accordion"
* @slot content - The content to show inside of the card.
@@ -31,6 +36,7 @@ export default class MozCard extends MozLitElement {
static properties = {
heading: { type: String },
+ icon: { type: Boolean },
type: { type: String, reflect: true },
expanded: { type: Boolean },
};
@@ -47,9 +53,15 @@ export default class MozCard extends MozLitElement {
}
return html`
<div id="heading-wrapper">
- ${this.type == "accordion"
- ? html` <div class="chevron-icon"></div>`
- : ""}
+ ${when(
+ this.type == "accordion",
+ () => html`<div class="chevron-icon"></div>`
+ )}
+ ${when(
+ this.icon,
+ () =>
+ html`<div part="icon" id="heading-icon" role="presentation"></div>`
+ )}
<span id="heading">${this.heading}</span>
</div>
`;
diff --git a/toolkit/content/widgets/moz-card/moz-card.stories.mjs b/toolkit/content/widgets/moz-card/moz-card.stories.mjs
index da3279b2a4..0430956a52 100644
--- a/toolkit/content/widgets/moz-card/moz-card.stories.mjs
+++ b/toolkit/content/widgets/moz-card/moz-card.stories.mjs
@@ -3,7 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// eslint-disable-next-line import/no-unresolved
-import { html, ifDefined } from "lit.all.mjs";
+import { classMap, html, ifDefined } from "lit.all.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "./moz-card.mjs";
@@ -15,6 +15,8 @@ export default {
fluent: `
moz-card-heading =
.heading = This is the label
+moz-card-heading-with-icon =
+ .heading = This is a card with a heading icon
`,
},
argTypes: {
@@ -22,13 +24,27 @@ moz-card-heading =
options: ["default", "accordion"],
control: { type: "select" },
},
+ hasHeadingIcon: {
+ options: [true, false],
+ control: { type: "select" },
+ },
},
};
-const Template = ({ l10nId, content, type }) => html`
- <main style="max-width: 400px">
+const Template = ({ l10nId, content, type, hasHeadingIcon }) => html`
+ <style>
+ main {
+ max-width: 400px;
+ }
+ moz-card.headingWithIcon::part(icon) {
+ background-image: url("chrome://browser/skin/preferences/category-general.svg");
+ }
+ </style>
+ <main>
<moz-card
type=${ifDefined(type)}
+ ?icon=${hasHeadingIcon}
+ class=${classMap({ headingWithIcon: hasHeadingIcon })}
data-l10n-id=${ifDefined(l10nId)}
data-l10n-attrs="heading"
>
@@ -86,3 +102,13 @@ CardTypeAccordion.parameters = {
},
},
};
+
+export const CardWithHeadingIcon = Template.bind({});
+CardWithHeadingIcon.args = {
+ l10nId: "moz-card-heading-with-icon",
+ content: `Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+ Nunc velit turpis, mollis a ultricies vitae, accumsan ut augue.
+ In a eros ac dolor hendrerit varius et at mauris.`,
+ type: "default",
+ hasHeadingIcon: true,
+};
diff --git a/toolkit/content/widgets/moz-message-bar/moz-message-bar.css b/toolkit/content/widgets/moz-message-bar/moz-message-bar.css
index 6d35009982..fa7b08612d 100644
--- a/toolkit/content/widgets/moz-message-bar/moz-message-bar.css
+++ b/toolkit/content/widgets/moz-message-bar/moz-message-bar.css
@@ -6,14 +6,7 @@
/* Icon */
--message-bar-icon-color: var(--icon-color-information);
--message-bar-icon-size: var(--size-item-small);
- --message-bar-icon-close-color: var(--icon-color);
- --message-bar-icon-close-url: url("chrome://global/skin/icons/close-12.svg");
-
- /* Button */
- --message-bar-button-size-ghost: var(--button-min-height);
- --message-bar-button-border-radius-ghost: var(--button-border-radius);
- --message-bar-button-background-color-ghost-hover: var(--button-background-color-hover);
- --message-bar-button-background-color-ghost-active: var(--button-background-color-active);
+ --message-bar-icon-close-url: url("chrome://global/skin/icons/close.svg");
/* Container */
--message-bar-container-min-height: var(--size-item-large);
@@ -28,12 +21,13 @@
--message-bar-text-line-height: 1.5em;
/* Background */
- --message-bar-background-color: var(--color-background-information);
+ --message-bar-background-color: var(--background-color-information);
background-color: var(--message-bar-background-color);
border: var(--message-bar-border-width) solid var(--message-bar-border-color);
border-radius: var(--message-bar-border-radius);
color: var(--message-bar-text-color);
+ text-align: start;
}
@media (prefers-contrast) {
@@ -144,31 +138,8 @@
/* Close icon styles */
-.close {
+moz-button::part(button) {
background-image: var(--message-bar-icon-close-url);
- background-repeat: no-repeat;
- background-position: center center;
- -moz-context-properties: fill;
- fill: currentColor;
- min-width: auto;
- min-height: auto;
- width: var(--message-bar-button-size-ghost);
- height: var(--message-bar-button-size-ghost);
- margin: 0;
- padding: 0;
- flex-shrink: 0;
-}
-
-.ghost-button {
- border-radius: var(--message-bar-button-border-radius-ghost);
-}
-
-.ghost-button:enabled:hover {
- background-color: var(--message-bar-button-background-color-ghost-hover);
-}
-
-.ghost-button:enabled:hover:active {
- background-color: var(--message-bar-button-background-color-ghost-active);
}
@media not (prefers-contrast) {
@@ -176,7 +147,7 @@
/* Colors from: https://www.figma.com/file/zd3B9UyknB2XNZNdrYLm2W/Outreachy?type=design&node-id=59-1921&mode=design&t=ZYS4e6pAbAlXGvun-4 */
:host([type=warning]) {
- --message-bar-background-color: var(--color-background-warning);
+ --message-bar-background-color: var(--background-color-warning);
.icon {
--message-bar-icon-color: var(--icon-color-warning);
@@ -184,7 +155,7 @@
}
:host([type=success]) {
- --message-bar-background-color: var(--color-background-success);
+ --message-bar-background-color: var(--background-color-success);
.icon {
--message-bar-icon-color: var(--icon-color-success);
@@ -193,19 +164,10 @@
:host([type=error]),
:host([type=critical]) {
- --message-bar-background-color: var(--color-background-critical);
+ --message-bar-background-color: var(--background-color-critical);
.icon {
--message-bar-icon-color: var(--icon-color-critical);
}
}
-
- .close {
- fill: var(--message-bar-icon-close-color);
- }
-
- .ghost-button {
- border: none;
- background-color: transparent;
- }
}
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 d83de5d29f..8f0c997149 100644
--- a/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
+++ b/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs
@@ -4,6 +4,8 @@
import { html, ifDefined } from "../vendor/lit.all.mjs";
import { MozLitElement } from "../lit-utils.mjs";
+// eslint-disable-next-line import/no-unassigned-import
+import "chrome://global/content/elements/moz-button.mjs";
const messageTypeToIconData = {
info: {
@@ -41,7 +43,7 @@ const messageTypeToIconData = {
* @property {string} messageL10nArgs - Any args needed for the message l10n ID.
* @fires message-bar:close
* Custom event indicating that message bar was closed.
- * @fires message-bar:user-dismissed
+ * @fires message-bar:user-dismissed
* Custom event indicating that message bar was dismissed by the user.
*/
@@ -49,7 +51,7 @@ export default class MozMessageBar extends MozLitElement {
static queries = {
actionsSlotEl: "slot[name=actions]",
actionsEl: ".actions",
- closeButtonEl: "button.close",
+ closeButtonEl: "moz-button.close",
supportLinkSlotEl: "slot[name=support-link]",
};
@@ -113,14 +115,16 @@ export default class MozMessageBar extends MozLitElement {
return "";
}
- closeButtonTemplate() {
+ closeButtonTemplate({ size } = {}) {
if (this.dismissable) {
return html`
- <button
- class="close ghost-button"
+ <moz-button
+ type="icon ghost"
+ class="close"
+ size=${ifDefined(size)}
data-l10n-id="moz-message-bar-close-button"
@click=${this.dismiss}
- ></button>
+ ></moz-button>
`;
}
return "";
diff --git a/toolkit/content/widgets/moz-page-nav/README.stories.md b/toolkit/content/widgets/moz-page-nav/README.stories.md
new file mode 100644
index 0000000000..800d446478
--- /dev/null
+++ b/toolkit/content/widgets/moz-page-nav/README.stories.md
@@ -0,0 +1,98 @@
+# MozPageNav
+
+`moz-page-nav` is 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.
+
+```html story
+<moz-page-nav heading="This is a nav" style={{ '--page-nav-margin-top': 0, '--page-nav-margin-bottom': 0, height: '200px' }}>
+ <moz-page-nav-button
+ view="view-one"
+ iconSrc="chrome://browser/skin/preferences/category-general.svg"
+ >
+ <p style={{ margin: 0 }}>Test 1</p>
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="view-two"
+ iconSrc="chrome://browser/skin/preferences/category-general.svg"
+ >
+ <p style={{ margin: 0 }}>Test 2</p>
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="view-three"
+ iconSrc="chrome://browser/skin/preferences/category-general.svg"
+ >
+ <p style={{ margin: 0 }}>Test 3</p>
+ </moz-page-nav-button>
+</moz-page-nav>
+```
+
+## When to use
+
+* Use moz-page-nav for single-page navigation to switch between different views.
+* moz-page-nav will also support footer buttons for external support links in the future (See [bug 1877826](https://bugzilla.mozilla.org/show_bug.cgi?id=1877826))
+* This component will be used in about: pages such as about:firefoxview, about:preferences, about:addons, about:debugging, etc.
+
+## When not to use
+
+* If you need a navigation menu that does not switch between views within a single page
+
+## Code
+
+The source for `moz-page-nav` and `moz-page-nav-button` can be found under
+[toolkit/content/widgets/moz-page-nav](https://searchfox.org/mozilla-central/source/toolkit/content/widgets/moz-page-nav).
+You can find an examples of `moz-page-nav` in use in the Firefox codebase in
+[about:firefoxview](https://searchfox.org/mozilla-central/source/browser/components/firefoxview/firefoxview.html#52-87).
+
+`moz-page-nav` can be imported into `.html`/`.xhtml` files:
+
+```html
+<script type="module" src="chrome://global/content/elements/moz-page-nav.mjs"></script>
+```
+
+And used as follows:
+
+```html
+<moz-page-nav>
+ <moz-page-nav-button
+ view="A name for the first view"
+ iconSrc="A url for the icon for the first navigation button">
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="A name for the second view"
+ iconSrc="A url for the icon for the second navigation button">
+ </moz-page-nav-button>
+ <moz-page-nav-button
+ view="A name for the third view"
+ iconSrc="A url for the icon for the third navigation button">
+ </moz-page-nav-button>
+</moz-page-nav>
+```
+
+### Fluent usage
+
+Generally the `heading` property of
+`moz-page-nav` will be provided via [Fluent attributes](https://mozilla-l10n.github.io/localizer-documentation/tools/fluent/basic_syntax.html#attributes).
+To get this working you will need to specify a `data-l10n-id` as well as
+`data-l10n-attrs` if you're providing a heading:
+
+```html
+<moz-page-nav data-l10n-id="with-heading"
+ data-l10n-attrs="heading"></moz-page-nav>
+```
+
+In which case your Fluent messages will look something like this:
+
+```
+with-heading =
+ .heading = Heading text goes here
+```
+
+You also need to specify a `data-l10n-id` for each `moz-page-nav-button`:
+
+```html
+<moz-page-nav-button data-l10n-id="with-button-text"></moz-page-nav-button>
+```
+
+```
+with-button-text = button text goes here
+```
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
index 2975bb1a7c..5d00198d65 100644
--- a/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav-button.css
@@ -13,7 +13,7 @@ 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;
+ fill: var(--icon-color);
display: grid;
grid-template-columns: min-content 1fr;
gap: 12px;
@@ -34,34 +34,35 @@ button:hover {
@media not (prefers-contrast) {
button {
- border-inline-start: 2px solid transparent;
- border-inline-end: none;
- border-block: none;
+ position: relative;
}
- button:hover,
- button[selected]:hover {
- background-color: var(--page-nav-button-background-color-hover);
+ button::before {
+ content: "";
+ display: block;
+ position: absolute;
+ inset-block: 0;
+ inset-inline-start: 0;
+ width: 2px;
+ background-color: transparent;
}
- button[selected]:hover {
- border-inline-start-color: inherit;
+ button[selected]::before {
+ background-color: var(--color-accent-primary);
}
- button[selected],
- button[selected]:hover {
- border-inline-start: 2px solid;
+ button[selected]:hover::before {
+ background-color: var(--icon-color);
}
- button[selected]:not(:focus-visible) {
- border-start-start-radius: 0;
- border-end-start-radius: 0;
+ button:hover,
+ button[selected]:hover {
+ background-color: var(--page-nav-button-background-color-hover);
}
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);
}
}
@@ -74,7 +75,7 @@ button:hover {
button:focus-visible,
button[selected]:focus-visible {
outline: var(--focus-outline);
- outline-offset: var(--focus-outline-offset);
+ outline-offset: 0;
border-radius: var(--border-radius-small);
}
diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.css b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css
index 49000f622d..8e6724b2f5 100644
--- a/toolkit/content/widgets/moz-page-nav/moz-page-nav.css
+++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.css
@@ -20,6 +20,7 @@
position: sticky;
top: 0;
height: 100vh;
+ display: block;
width: var(--page-nav-width);
@media (prefers-reduced-motion) {
@@ -38,7 +39,7 @@ nav {
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));
+ height: calc(100% - var(--page-nav-margin-top) - var(--page-nav-margin-bottom));
@media (max-width: 52rem) {
grid-template-rows: 1fr auto;
}
diff --git a/toolkit/content/widgets/moz-toggle/moz-toggle.css b/toolkit/content/widgets/moz-toggle/moz-toggle.css
index 2005544181..ba98e80021 100644
--- a/toolkit/content/widgets/moz-toggle/moz-toggle.css
+++ b/toolkit/content/widgets/moz-toggle/moz-toggle.css
@@ -37,7 +37,7 @@
--toggle-background-color-pressed: var(--color-accent-primary);
--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: var(--border-color-interactive);
--toggle-border-color-hover: var(--toggle-border-color);
--toggle-border-color-active: var(--toggle-border-color);
--toggle-border-radius: var(--border-radius-circle);
@@ -47,7 +47,7 @@
--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-background-color-on-pressed: var(--background-color-canvas);
--toggle-dot-margin: 1px;
--toggle-dot-height: calc(var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 * var(--toggle-border-width));
--toggle-dot-width: var(--toggle-dot-height);
@@ -77,7 +77,7 @@
border-color: var(--toggle-border-color);
}
-.toggle-button:enabled:active {
+.toggle-button:enabled:hover:active {
background: var(--toggle-background-color-active);
border-color: var(--toggle-border-color);
}
@@ -92,7 +92,7 @@
border-color: transparent;
}
-.toggle-button[aria-pressed="true"]:enabled:active {
+.toggle-button[aria-pressed="true"]:enabled:hover:active {
background: var(--toggle-background-color-pressed-active);
border-color: transparent;
}
@@ -114,7 +114,7 @@
}
.toggle-button[aria-pressed="true"]:enabled:hover::before,
-.toggle-button[aria-pressed="true"]:enabled:active::before {
+.toggle-button[aria-pressed="true"]:enabled:hover:active::before {
background-color: var(--toggle-dot-background-color-on-pressed);
}
@@ -149,7 +149,7 @@
border-color: var(--toggle-border-color-hover);
}
- .toggle-button:enabled:active {
+ .toggle-button:enabled:hover:active {
border-color: var(--toggle-border-color-active);
}
@@ -163,13 +163,13 @@
border-color: var(--toggle-border-color-hover);
}
- .toggle-button[aria-pressed="true"]:enabled:active {
+ .toggle-button[aria-pressed="true"]:enabled:hover:active {
background-color: var(--toggle-dot-background-color-active);
border-color: var(--toggle-dot-background-color-hover);
}
.toggle-button:hover::before,
- .toggle-button:active::before {
+ .toggle-button:hover:active::before {
background-color: var(--toggle-dot-background-color-hover);
}
}
@@ -181,9 +181,9 @@
--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-border-color-hover: var(--border-color-interactive-hover);
+ --toggle-border-color-active: var(--border-color-interactive-active);
+ --toggle-border-color-disabled: var(--border-color-interactive-disabled);
}
.toggle-button[aria-pressed="true"]:enabled::after {
@@ -197,7 +197,7 @@
inset: -2px;
}
- .toggle-button[aria-pressed="true"]:enabled:active::after {
+ .toggle-button[aria-pressed="true"]:enabled:hover:active::after {
border-color: var(--toggle-border-color-active);
}
}
diff --git a/toolkit/content/widgets/notificationbox.js b/toolkit/content/widgets/notificationbox.js
index 0161588853..8cfc7b865c 100644
--- a/toolkit/content/widgets/notificationbox.js
+++ b/toolkit/content/widgets/notificationbox.js
@@ -663,6 +663,10 @@
}
}
+ closeButtonTemplate() {
+ return super.closeButtonTemplate({ size: "small" });
+ }
+
#setStyles() {
let style = document.createElement("link");
style.rel = "stylesheet";
diff --git a/toolkit/content/widgets/radio.js b/toolkit/content/widgets/radio.js
index 41e8a945ba..52ace9bf6b 100644
--- a/toolkit/content/widgets/radio.js
+++ b/toolkit/content/widgets/radio.js
@@ -214,7 +214,7 @@
}
get value() {
- return this.getAttribute("value");
+ return this.getAttribute("value") || "";
}
set disabled(val) {
@@ -526,7 +526,7 @@
}
get value() {
- return this.getAttribute("value");
+ return this.getAttribute("value") || "";
}
get selected() {
diff --git a/toolkit/content/widgets/richlistbox.js b/toolkit/content/widgets/richlistbox.js
index 01d970e6ed..0d669c4c5e 100644
--- a/toolkit/content/widgets/richlistbox.js
+++ b/toolkit/content/widgets/richlistbox.js
@@ -109,14 +109,14 @@
for (var i = 0; i < rowCount; i++) {
var k = (start + i) % rowCount;
var listitem = this.getItemAtIndex(k);
- if (!this._canUserSelect(listitem)) {
+ if (!this.canUserSelect(listitem)) {
continue;
}
// allow richlistitems to specify the string being searched for
var searchText =
"searchLabel" in listitem
? listitem.searchLabel
- : listitem.getAttribute("label"); // (see also bug 250123)
+ : listitem.getAttribute("label") || ""; // (see also bug 250123)
searchText = searchText.substring(0, length).toLowerCase();
if (searchText == incrementalString) {
this.ensureIndexIsVisible(k);
@@ -227,7 +227,7 @@
this.setAttribute("seltype", val);
}
get selType() {
- return this.getAttribute("seltype");
+ return this.getAttribute("seltype") || "";
}
// nsIDOMXULSelectControlElement
@@ -337,7 +337,7 @@
if (
aStartItem &&
aStartItem.localName == "richlistitem" &&
- (!this._userSelecting || this._canUserSelect(aStartItem))
+ (!this._userSelecting || this.canUserSelect(aStartItem))
) {
--aDelta;
if (aDelta == 0) {
@@ -354,7 +354,7 @@
if (
aStartItem &&
aStartItem.localName == "richlistitem" &&
- (!this._userSelecting || this._canUserSelect(aStartItem))
+ (!this._userSelecting || this.canUserSelect(aStartItem))
) {
--aDelta;
if (aDelta == 0) {
@@ -771,7 +771,7 @@
var newItem = this.getItemAtIndex(newIndex);
// make sure that the item is actually visible/selectable
- if (this._userSelecting && newItem && !this._canUserSelect(newItem)) {
+ if (this._userSelecting && newItem && !this.canUserSelect(newItem)) {
newItem =
aOffset > 0
? this.getNextItem(newItem, 1) || this.getPreviousItem(newItem, 1)
@@ -811,7 +811,11 @@
}
}
- _canUserSelect(aItem) {
+ canUserSelect(aItem) {
+ if (aItem.disabled) {
+ return false;
+ }
+
var style = document.defaultView.getComputedStyle(aItem);
return (
style.display != "none" &&
@@ -886,7 +890,7 @@
*/
this.addEventListener("mousedown", event => {
var control = this.control;
- if (!control || control.disabled) {
+ if (!control || this.disabled || control.disabled) {
return;
}
if (
@@ -912,7 +916,7 @@
}
var control = this.control;
- if (!control || control.disabled) {
+ if (!control || this.disabled || control.disabled) {
return;
}
control._userSelecting = true;
@@ -977,7 +981,7 @@
}
get value() {
- return this.getAttribute("value");
+ return this.getAttribute("value") || "";
}
/**
diff --git a/toolkit/content/widgets/tabbox.js b/toolkit/content/widgets/tabbox.js
index b1b2ddecce..f4003d002c 100644
--- a/toolkit/content/widgets/tabbox.js
+++ b/toolkit/content/widgets/tabbox.js
@@ -459,7 +459,7 @@
}
get value() {
- return this.getAttribute("value");
+ return this.getAttribute("value") || "";
}
get control() {
@@ -563,7 +563,7 @@
}
get value() {
- return this.getAttribute("value");
+ return this.getAttribute("value") || "";
}
get tabbox() {
diff --git a/toolkit/content/widgets/tree.js b/toolkit/content/widgets/tree.js
index 4993bef0c2..a871d3396f 100644
--- a/toolkit/content/widgets/tree.js
+++ b/toolkit/content/widgets/tree.js
@@ -1078,7 +1078,7 @@
}
get selType() {
- return this.getAttribute("seltype");
+ return this.getAttribute("seltype") || "";
}
set currentIndex(val) {
diff --git a/toolkit/content/widgets/wizard.js b/toolkit/content/widgets/wizard.js
index c4285fada5..8a3b9c164d 100644
--- a/toolkit/content/widgets/wizard.js
+++ b/toolkit/content/widgets/wizard.js
@@ -222,7 +222,7 @@
cp &&
((this._accessMethod == "sequential" &&
cp.pageIndex == this.pageCount - 1) ||
- (this._accessMethod == "random" && cp.next == ""))
+ (this._accessMethod == "random" && !cp.next))
);
}
@@ -381,7 +381,7 @@
aPage.pageIndex = this.pageCount;
this.pageCount += 1;
if (!this._accessMethod) {
- this._accessMethod = aPage.next == "" ? "sequential" : "random";
+ this._accessMethod = aPage.next ? "random" : "sequential";
}
if (!this._maybeStartWizard() && this._hasStarted) {
// If the wizard has already started, adding a page might require