diff options
Diffstat (limited to 'toolkit/content/widgets')
20 files changed, 276 insertions, 405 deletions
diff --git a/toolkit/content/widgets/arrowscrollbox.js b/toolkit/content/widgets/arrowscrollbox.js index 7109891faf..5983435d06 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" data-l10n-id="overflow-scroll-button-up"/> + <toolbarbutton id="scrollbutton-up" part="scrollbutton-up" keyNav="false" data-l10n-id="overflow-scroll-button-backwards"/> <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" data-l10n-id="overflow-scroll-button-down"/> + <toolbarbutton id="scrollbutton-down" part="scrollbutton-down" keyNav="false" data-l10n-id="overflow-scroll-button-forwards"/> `; } diff --git a/toolkit/content/widgets/autocomplete-richlistitem.js b/toolkit/content/widgets/autocomplete-richlistitem.js index b339ab1e27..0ec8a19243 100644 --- a/toolkit/content/widgets/autocomplete-richlistitem.js +++ b/toolkit/content/widgets/autocomplete-richlistitem.js @@ -832,9 +832,7 @@ setTimeout(() => { let selectedIndex = popup ? popup.selectedIndex : -1; - actor.manager - .getActor("FormAutofill") - .sendAsyncMessage("FormAutofill:PreviewProfile", { selectedIndex }); + actor.previewAutofillProfile(selectedIndex); }, 0); } diff --git a/toolkit/content/widgets/datetimebox.js b/toolkit/content/widgets/datetimebox.js index 04ed398bd7..1c63b09269 100644 --- a/toolkit/content/widgets/datetimebox.js +++ b/toolkit/content/widgets/datetimebox.js @@ -650,6 +650,10 @@ this.DateTimeBoxWidget = class { onKeyDown(aEvent) { this.log("onKeyDown key: " + aEvent.key); + if (aEvent.defaultPrevented) { + return; + } + switch (aEvent.key) { // Toggle the picker on Space/Enter on Calendar button or Space on input, // close on Escape anywhere. @@ -691,21 +695,17 @@ this.DateTimeBoxWidget = class { aEvent.preventDefault(); break; } - if (this.isEditable()) { - // TODO(emilio, bug 1571533): These functions should look at - // defaultPrevented. - // Ctrl+Backspace/Delete on non-macOS and - // Cmd+Backspace/Delete on macOS to clear the field - if (aEvent.getModifierState("Accel")) { - // Clear the input's value - this.clearInputFields(false); - } else { - let targetField = aEvent.originalTarget; - this.clearFieldValue(targetField); - this.setInputValueFromFields(); - } - aEvent.preventDefault(); + // Ctrl+Backspace/Delete on non-macOS and + // Cmd+Backspace/Delete on macOS to clear the field + if (aEvent.getModifierState("Accel")) { + // Clear the input's value + this.clearInputFields(false); + } else { + let targetField = aEvent.originalTarget; + this.clearFieldValue(targetField); + this.setInputValueFromFields(); } + aEvent.preventDefault(); break; } case "ArrowRight": diff --git a/toolkit/content/widgets/message-bar.css b/toolkit/content/widgets/message-bar.css deleted file mode 100644 index eddc5a3ae6..0000000000 --- a/toolkit/content/widgets/message-bar.css +++ /dev/null @@ -1,219 +0,0 @@ -/* 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 { - --info-icon-url: url("chrome://global/skin/icons/info-filled.svg"); - --warn-icon-url: url("chrome://global/skin/icons/warning.svg"); - --success-icon-url: url("chrome://global/skin/icons/check.svg"); - --error-icon-url: url("chrome://global/skin/icons/error.svg"); - --close-icon-url: url("chrome://global/skin/icons/close-12.svg"); - --close-fill-color: var(--in-content-icon-color); - --icon-size: 16px; - --close-icon-size: 28px; -} - -:host { - --message-bar-background-color: var(--in-content-box-info-background); - --message-bar-text-color: var(--in-content-text-color); - --message-bar-icon-url: var(--info-icon-url); - /* The default values of --in-content-button* are sufficient, even for dark themes */ -} - -:host([type=warning]) { - --message-bar-icon-url: var(--warn-icon-url); -} - -:host([type=success]) { - --message-bar-icon-url: var(--success-icon-url); -} - -:host([type=error]), -:host([type=critical]) { - --message-bar-icon-url: var(--error-icon-url); -} - -:host { - border: 1px solid transparent; - border-radius: 4px; -} - -/* Make the host to behave as a block by default, but allow hidden to hide it. */ -:host(:not([hidden])) { - display: block; -} - -::slotted(button) { - /* Enforce micro-button width. */ - min-width: -moz-fit-content !important; -} - -/* MessageBar Grid Layout */ - -.container { - background: var(--message-bar-background-color); - color: var(--message-bar-text-color); - - padding: 3px 7px; - position: relative; - - border-radius: 4px; - - display: flex; - /* Ensure that the message bar shadow dom elements are vertically aligned. */ - align-items: center; -} - -:host([align="center"]) .container { - justify-content: center; -} - -.content { - margin: 0 4px; - display: inline-block; - /* Ensure that the message bar content is vertically aligned. */ - align-items: center; - /* Ensure that the message bar content is wrapped. */ - word-break: break-word; -} - -/* MessageBar icon style */ - -.icon { - padding: 4px; - width: var(--icon-size); - height: var(--icon-size); - flex-shrink: 0; -} - -.icon::after { - display: inline-block; - appearance: none; - -moz-context-properties: fill, stroke; - fill: currentColor; - stroke: currentColor; - content: ""; - background-image: var(--message-bar-icon-url); - background-size: var(--icon-size); - width: var(--icon-size); - height: var(--icon-size); -} - -/* Use a spacer to position the close button at the end, but also support - * centering if required. */ -.spacer { - flex-grow: 1; -} - -/* Close icon styles */ - -:host(:not([dismissable])) .close { - display: none; -} - -.close { - background-image: var(--close-icon-url); - background-repeat: no-repeat; - background-position: center center; - -moz-context-properties: fill; - fill: currentColor; - min-width: auto; - min-height: auto; - width: var(--close-icon-size); - height: var(--close-icon-size); - padding: 0; - flex-shrink: 0; - margin: 4px 8px; - background-size: 12px; -} - -@media (prefers-contrast) { - :host { - border-color: CanvasText; - } -} - -@media not (prefers-contrast) { - /* MessageBar colors by message type */ - /* Colors from: https://design.firefox.com/photon/components/message-bars.html#type-specific-style */ - - :host([type=warning]) { - /* Ensure colors within the bar are adjusted and controls are readable */ - color-scheme: light; - - --message-bar-background-color: var(--yellow-50); - --message-bar-text-color: var(--yellow-90); - - --in-content-button-background: var(--yellow-60); - --in-content-button-background-hover: var(--yellow-70); - --in-content-button-background-active: var(--yellow-80); - - --close-fill-color: var(--message-bar-text-color); - } - - :host([type=success]) { - /* Ensure colors within the bar are adjusted and controls are readable */ - color-scheme: light; - - --message-bar-background-color: var(--green-50); - --message-bar-text-color: var(--green-90); - - --in-content-button-background: var(--green-60); - --in-content-button-background-hover: var(--green-70); - --in-content-button-background-active: var(--green-80); - } - - :host([type=error]) { - --message-bar-background-color: var(--red-60); - --message-bar-text-color: #ffffff; - - --in-content-button-background: var(--red-70); - --in-content-button-background-hover: var(--red-80); - --in-content-button-background-active: var(--red-90); - } - - :host([type=info]) .icon { - color: rgb(0,144,237); - } - - :host([type=warning]) .icon { - color: rgb(255,164,54); - } - - :host([type=critical]) .icon { - color: rgb(226,40,80); - } - - .close { - fill: var(--close-fill-color); - } - - @media (prefers-color-scheme: dark) { - /* Don't set the background in prefers-contrast mode or macOS can end up - * with black on black text. */ - :host([type=info]) .icon { - color: rgb(128,235,255); - } - - :host([type=warning]) .icon { - color: rgb(255,189,79); - } - - :host([type=critical]) .icon { - color: rgb(255,154,162); - } - } -} - -strong { - font-weight: 600; -} - -.text-link:hover { - cursor: pointer; -} - -@keyframes spin { - from { transform: rotate(0); } - to { transform: rotate(360deg); } -} diff --git a/toolkit/content/widgets/message-bar.js b/toolkit/content/widgets/message-bar.js deleted file mode 100644 index d38347b40a..0000000000 --- a/toolkit/content/widgets/message-bar.js +++ /dev/null @@ -1,91 +0,0 @@ -/* 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/. */ - -"use strict"; - -// This is loaded into chrome windows with the subscript loader. Wrap in -// a block to prevent accidentally leaking globals onto `window`. -{ - class MessageBarElement extends HTMLElement { - constructor() { - super(); - const shadowRoot = this.attachShadow({ mode: "open" }); - window.MozXULElement?.insertFTLIfNeeded( - "toolkit/global/notification.ftl" - ); - document.l10n.connectRoot(this.shadowRoot); - const content = this.constructor.template.content.cloneNode(true); - shadowRoot.append(content); - this.closeButton.addEventListener("click", () => this.dismiss(), { - once: true, - }); - } - - disconnectedCallback() { - this.dispatchEvent(new CustomEvent("message-bar:close")); - } - - get closeButton() { - return this.shadowRoot.querySelector("button.close"); - } - - static get template() { - const template = document.createElement("template"); - - const commonStyles = document.createElement("link"); - commonStyles.rel = "stylesheet"; - commonStyles.href = "chrome://global/skin/in-content/common.css"; - const messageBarStyles = document.createElement("link"); - messageBarStyles.rel = "stylesheet"; - messageBarStyles.href = - "chrome://global/content/elements/message-bar.css"; - template.content.append(commonStyles, messageBarStyles); - - // A container for the entire message bar content, - // most of the css rules needed to provide the - // expected message bar layout is applied on this - // element. - const container = document.createElement("div"); - container.part = "container"; - container.classList.add("container"); - template.content.append(container); - - const icon = document.createElement("span"); - icon.classList.add("icon"); - icon.part = "icon"; - container.append(icon); - - const barcontent = document.createElement("span"); - barcontent.classList.add("content"); - barcontent.append(document.createElement("slot")); - container.append(barcontent); - - const spacer = document.createElement("span"); - spacer.classList.add("spacer"); - container.append(spacer); - - const closeIcon = document.createElement("button"); - closeIcon.classList.add("close", "ghost-button"); - document.l10n.setAttributes(closeIcon, "notification-close-button"); - container.append(closeIcon); - - Object.defineProperty(this, "template", { - value: template, - }); - - return template; - } - - dismiss() { - this.dispatchEvent(new CustomEvent("message-bar:user-dismissed")); - this.close(); - } - - close() { - this.remove(); - } - } - - customElements.define("message-bar", MessageBarElement); -} diff --git a/toolkit/content/widgets/moz-button-group/moz-button-group.css b/toolkit/content/widgets/moz-button-group/moz-button-group.css index ba79d69e12..5b6f7deace 100644 --- a/toolkit/content/widgets/moz-button-group/moz-button-group.css +++ b/toolkit/content/widgets/moz-button-group/moz-button-group.css @@ -11,6 +11,7 @@ margin: 0 !important; } -::slotted(button:not(:first-child, .popup-notification-dropmarker)) { +::slotted(button:not(:first-child, .popup-notification-dropmarker)), +::slotted(moz-button:not(:first-child)) { margin-inline-start: var(--space-small) !important; } 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 8bf553c23d..6858367ab7 100644 --- a/toolkit/content/widgets/moz-button-group/moz-button-group.mjs +++ b/toolkit/content/widgets/moz-button-group/moz-button-group.mjs @@ -50,15 +50,22 @@ export default class MozButtonGroup extends MozLitElement { // Text nodes won't support classList or getAttribute. continue; } - // Bug 1791816: These should check moz-button instead of button. - if ( - child.localName == "button" && - (child.classList.contains("primary") || - child.getAttribute("type") == "submit" || - child.hasAttribute("autofocus") || - child.hasAttribute("default")) - ) { - child.slot = "primary"; + switch (child.localName) { + case "button": + if ( + child.classList.contains("primary") || + child.getAttribute("type") == "submit" || + child.hasAttribute("autofocus") || + child.hasAttribute("default") + ) { + child.slot = "primary"; + } + break; + case "moz-button": + if (child.type == "primary" || child.type == "destructive") { + child.slot = "primary"; + } + break; } } this.#reorderLightDom(); diff --git a/toolkit/content/widgets/moz-button/moz-button.css b/toolkit/content/widgets/moz-button/moz-button.css index 71d57ea93a..4eb6839e06 100644 --- a/toolkit/content/widgets/moz-button/moz-button.css +++ b/toolkit/content/widgets/moz-button/moz-button.css @@ -23,6 +23,10 @@ button { /* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */ font-size: var(--button-font-size); width: 100%; + display: flex; + justify-content: center; + align-items: center; + gap: var(--space-small); &[size=small] { min-height: var(--button-min-height-small); @@ -125,21 +129,33 @@ button { } } - &[type~=icon] { + &[type~=icon]:not(.labelled) { background-size: var(--icon-size-default); background-position: center; background-repeat: no-repeat; - -moz-context-properties: fill, stroke; - fill: currentColor; - stroke: currentColor; + } + + &[type~=icon]:not(.labelled), + &:not(.labelled):has(img) { 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); height: var(--button-size-icon-small); } } + + img, + &[type~=icon]:not(.labelled) { + -moz-context-properties: fill, fill-opacity, stroke; + fill: currentColor; + stroke: currentColor; + } + + img { + width: var(--icon-size-default); + height: var(--icon-size-default); + } } diff --git a/toolkit/content/widgets/moz-button/moz-button.mjs b/toolkit/content/widgets/moz-button/moz-button.mjs index a951dbf5d8..9a239d4e56 100644 --- a/toolkit/content/widgets/moz-button/moz-button.mjs +++ b/toolkit/content/widgets/moz-button/moz-button.mjs @@ -2,7 +2,7 @@ * 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 { html, ifDefined, classMap } from "../vendor/lit.all.mjs"; import { MozLitElement } from "../lit-utils.mjs"; /** @@ -19,8 +19,11 @@ import { MozLitElement } from "../lit-utils.mjs"; * @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} iconSrc - Path to the icon that should be displayed in the button. * @property {string} ariaLabelAttribute - Internal, map aria-label attribute to the ariaLabel JS property. + * @property {string} hasVisibleLabel - Internal, tracks whether or not the button has a visible label. * @property {HTMLButtonElement} buttonEl - The internal button element in the shadow DOM. + * @property {HTMLButtonElement} slotEl - The internal slot element in the shadow DOM. * @slot default - The button's content, overrides label property. * @fires click - The click event. */ @@ -44,10 +47,13 @@ export default class MozButton extends MozLitElement { reflect: true, }, ariaLabel: { type: String, state: true }, + iconSrc: { type: String }, + hasVisibleLabel: { type: Boolean, state: true }, }; static queries = { buttonEl: "button", + slotEl: "slot", }; constructor() { @@ -55,6 +61,7 @@ export default class MozButton extends MozLitElement { this.type = "default"; this.size = "default"; this.disabled = false; + this.hasVisibleLabel = !!this.label; } willUpdate(changes) { @@ -73,6 +80,12 @@ export default class MozButton extends MozLitElement { this.buttonEl.click(); } + checkForLabelText() { + this.hasVisibleLabel = this.slotEl + .assignedNodes() + .some(node => node.textContent.trim()); + } + render() { return html` <link @@ -86,8 +99,12 @@ export default class MozButton extends MozLitElement { title=${ifDefined(this.title || this.tooltipText)} aria-label=${ifDefined(this.ariaLabel)} part="button" + class=${classMap({ labelled: this.label || this.hasVisibleLabel })} > - <slot>${this.label}</slot> + ${this.iconSrc + ? html`<img src=${this.iconSrc} role="presentation" />` + : ""} + <slot @slotchange=${this.checkForLabelText}>${this.label}</slot> </button> `; } diff --git a/toolkit/content/widgets/moz-button/moz-button.stories.mjs b/toolkit/content/widgets/moz-button/moz-button.stories.mjs index 52a459e807..dd8d6369db 100644 --- a/toolkit/content/widgets/moz-button/moz-button.stories.mjs +++ b/toolkit/content/widgets/moz-button/moz-button.stories.mjs @@ -2,7 +2,7 @@ * 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"; +import { html, ifDefined } from "../vendor/lit.all.mjs"; // eslint-disable-next-line import/no-unassigned-import import "./moz-button.mjs"; @@ -22,6 +22,10 @@ export default { options: ["default", "small"], control: { type: "radio" }, }, + type: { + options: ["default", "primary", "destructive", "icon", "icon ghost"], + control: { type: "select" }, + }, }, parameters: { actions: { @@ -40,17 +44,13 @@ moz-button-aria-labelled = }, }; -const Template = ({ type, size, l10nId, iconUrl, disabled }) => html` - <style> - moz-button[type~="icon"]::part(button) { - background-image: url("${iconUrl}"); - } - </style> +const Template = ({ type, size, l10nId, iconSrc, disabled }) => html` <moz-button data-l10n-id=${l10nId} type=${type} size=${size} ?disabled=${disabled} + iconSrc=${ifDefined(iconSrc)} ></moz-button> `; @@ -59,7 +59,7 @@ Default.args = { type: "default", size: "default", l10nId: "moz-button-labelled", - iconUrl: "chrome://global/skin/icons/more.svg", + iconSrc: "", disabled: false, }; export const DefaultSmall = Template.bind({}); @@ -67,7 +67,7 @@ DefaultSmall.args = { type: "default", size: "small", l10nId: "moz-button-labelled", - iconUrl: "chrome://global/skin/icons/more.svg", + iconSrc: "", disabled: false, }; export const Primary = Template.bind({}); @@ -85,7 +85,7 @@ Destructive.args = { export const Icon = Template.bind({}); Icon.args = { ...Default.args, - type: "icon", + iconSrc: "chrome://global/skin/icons/more.svg", l10nId: "moz-button-titled", }; export const IconSmall = Template.bind({}); @@ -96,5 +96,12 @@ IconSmall.args = { export const IconGhost = Template.bind({}); IconGhost.args = { ...Icon.args, - type: "icon ghost", + type: "ghost", +}; +export const IconText = Template.bind({}); +IconText.args = { + type: "default", + size: "default", + iconSrc: "chrome://global/skin/icons/edit-copy.svg", + l10nId: "moz-button-labelled", }; 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 8f0c997149..9a186b41c9 100644 --- a/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs +++ b/toolkit/content/widgets/moz-message-bar/moz-message-bar.mjs @@ -49,10 +49,10 @@ const messageTypeToIconData = { export default class MozMessageBar extends MozLitElement { static queries = { - actionsSlotEl: "slot[name=actions]", + actionsSlot: "slot[name=actions]", actionsEl: ".actions", - closeButtonEl: "moz-button.close", - supportLinkSlotEl: "slot[name=support-link]", + closeButton: "moz-button.close", + supportLinkSlot: "slot[name=support-link]", }; static properties = { @@ -72,13 +72,13 @@ export default class MozMessageBar extends MozLitElement { } onSlotchange() { - let actions = this.actionsSlotEl.assignedNodes(); + let actions = this.actionsSlot.assignedNodes(); this.actionsEl.classList.toggle("active", actions.length); } connectedCallback() { super.connectedCallback(); - this.setAttribute("role", "status"); + this.setAttribute("role", "alert"); } disconnectedCallback() { @@ -87,7 +87,7 @@ export default class MozMessageBar extends MozLitElement { } get supportLinkEls() { - return this.supportLinkSlotEl.assignedElements(); + return this.supportLinkSlot.assignedElements(); } iconTemplate() { diff --git a/toolkit/content/widgets/moz-page-nav/README.stories.md b/toolkit/content/widgets/moz-page-nav/README.stories.md index 800d446478..c2f1b37bb5 100644 --- a/toolkit/content/widgets/moz-page-nav/README.stories.md +++ b/toolkit/content/widgets/moz-page-nav/README.stories.md @@ -4,7 +4,7 @@ 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 heading="This is a nav" style={{ '--page-nav-margin-top': 0, '--page-nav-margin-bottom': 0, height: '275px' }}> <moz-page-nav-button view="view-one" iconSrc="chrome://browser/skin/preferences/category-general.svg" @@ -23,13 +23,27 @@ intended to change the selected view, provide a heading, and have links to exter > <p style={{ margin: 0 }}>Test 3</p> </moz-page-nav-button> + <moz-page-nav-button + support-page="test" + iconSrc="chrome://browser/skin/preferences/category-general.svg" + slot="secondary-nav" + > + <p style={{ margin: 0 }}>Support Link</p> + </moz-page-nav-button> + <moz-page-nav-button + href="https://www.example.com" + iconSrc="chrome://browser/skin/preferences/category-general.svg" + slot="secondary-nav" + > + <p style={{ margin: 0 }}>External Link</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)) +* moz-page-nav also supports footer buttons for external and support links * This component will be used in about: pages such as about:firefoxview, about:preferences, about:addons, about:debugging, etc. ## When not to use @@ -53,18 +67,34 @@ And used as follows: ```html <moz-page-nav> - <moz-page-nav-button + <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 + </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 + </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-button> + + <!-- Footer Links --> + + <!-- Support Link --> + <moz-page-nav-button + support-page="A name for a support link" + iconSrc="A url for the icon for the third navigation button" + slot="secondary-nav"> + </moz-page-nav-button> + + <!-- External Link --> + <moz-page-nav-button + href="A url for an external link" + iconSrc="A url for the icon for the third navigation button" + slot="secondary-nav"> + </moz-page-nav-button> </moz-page-nav> ``` 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 5d00198d65..781398056a 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 @@ -4,11 +4,13 @@ :host { border-radius: var(--border-radius-small); + font-size: var(--font-size-large); &:focus-visible { outline-offset: var(--page-nav-focus-outline-inset); } } +a[href], button { background-color: var(--page-nav-button-background-color); border: 1px solid var(--page-nav-border-color); @@ -28,11 +30,17 @@ button { padding: var(--page-nav-button-padding); } +a[href] { + text-decoration: none; + box-sizing: border-box; +} + button:hover { cursor: pointer; } @media not (prefers-contrast) { + a[href], button { position: relative; } @@ -55,6 +63,7 @@ button:hover { background-color: var(--icon-color); } + a[href]:hover, button:hover, button[selected]:hover { background-color: var(--page-nav-button-background-color-hover); @@ -72,6 +81,7 @@ button:hover { } } +a[href]:focus-visible, button:focus-visible, button[selected]:focus-visible { outline: var(--focus-outline); @@ -86,13 +96,24 @@ button[selected]:focus-visible { fill: currentColor; } +:host(.secondary-nav-item) { + font-size: var(--font-size-small); + + & .page-nav-icon { + height: var(--icon-size-default); + width: var(--icon-size-default); + } +} + @media (prefers-contrast) { + a[href], button { transition: none; border-color: ButtonText; background-color: var(--button-background-color); } + a[href]:hover, button:hover { color: SelectedItem; } @@ -105,13 +126,13 @@ button[selected]:focus-visible { } slot { - font-size: var(--font-size-large); margin: 0; padding-inline-start: 0; user-select: none; } @media (max-width: 52rem) { + a[href], button { grid-template-columns: min-content; justify-content: center; diff --git a/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs index f998ee735f..c720e76e26 100644 --- a/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.mjs @@ -4,6 +4,8 @@ import { html } from "chrome://global/content/vendor/lit.all.mjs"; import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; +// eslint-disable-next-line import/no-unassigned-import +import "chrome://global/content/elements/moz-support-link.mjs"; /** * A grouping of navigation buttons that is displayed at the page level, @@ -35,6 +37,14 @@ export default class MozPageNav extends MozLitElement { ); } + get secondaryNavButtons() { + return this.secondaryNavGroupSlot + .assignedNodes() + .filter( + node => node?.localName === "moz-page-nav-button" && !node.hidden + ); + } + onChangeView(e) { this.currentView = e.target.view; } @@ -69,6 +79,12 @@ export default class MozPageNav extends MozLitElement { } } + onSecondaryNavChange() { + this.secondaryNavGroupSlot.assignedElements()?.forEach(el => { + el.classList.add("secondary-nav-item"); + }); + } + render() { return html` <link @@ -89,7 +105,10 @@ export default class MozPageNav extends MozLitElement { ></slot> </div> <div id="secondary-nav-group" role="group"> - <slot name="secondary-nav" @keydown=${this.handleFocus}></slot> + <slot + name="secondary-nav" + @slotchange=${this.onSecondaryNavChange} + ></slot> </div> </nav> `; @@ -114,16 +133,18 @@ 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} href - (optional) The url for an external link if not a support page URL * @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. + * @property {string} supportPage - (optional) The short name for the support page a secondary link should launch to * @slot [default] - Used to append the l10n string to the button. */ export class MozPageNavButton extends MozLitElement { static properties = { iconSrc: { type: String }, - l10nId: { type: String }, + href: { type: String }, selected: { type: Boolean }, + supportPage: { type: String, attribute: "support-page" }, }; connectedCallback() { @@ -133,6 +154,7 @@ export class MozPageNavButton extends MozLitElement { static queries = { buttonEl: "button", + linkEl: "a", }; get view() { @@ -148,12 +170,15 @@ export class MozPageNavButton extends MozLitElement { ); } - render() { + itemTemplate() { + if (this.href || this.supportPage) { + return this.linkTemplate(); + } + return this.buttonTemplate(); + } + + buttonTemplate() { 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} @@ -161,10 +186,51 @@ export class MozPageNavButton extends MozLitElement { ?selected=${this.selected} @click=${this.activate} > - <img class="page-nav-icon" src=${this.iconSrc} /> - <slot></slot> + ${this.innerContentTemplate()} </button> `; } + + linkTemplate() { + if (this.supportPage) { + return html` + <a + is="moz-support-link" + class="moz-page-nav-link" + support-page=${this.supportPage} + > + ${this.innerContentTemplate()} + </a> + `; + } + return html` + <a href=${this.href} class="moz-page-nav-link" target="_blank"> + ${this.innerContentTemplate()} + </a> + `; + } + + innerContentTemplate() { + return html` + ${this.iconSrc + ? html`<img + class="page-nav-icon" + src=${this.iconSrc} + role="presentation" + />` + : ""} + <slot></slot> + `; + } + + render() { + return html` + <link + rel="stylesheet" + href="chrome://global/content/elements/moz-page-nav-button.css" + /> + ${this.itemTemplate()} + `; + } } 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 index 4ac7b455cf..d1c565efe9 100644 --- a/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs +++ b/toolkit/content/widgets/moz-page-nav/moz-page-nav.stories.mjs @@ -2,7 +2,7 @@ * 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"; +import { html, when } from "../vendor/lit.all.mjs"; // eslint-disable-next-line import/no-unassigned-import import "./moz-page-nav.mjs"; @@ -21,15 +21,17 @@ 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-button-four = Support Link + .title = Support Link +moz-page-nav-button-five = External Link + .title = External Link moz-page-nav-heading = .heading = Heading `, }, }; -const Template = () => html` +const Template = ({ hasFooterLinks }) => html` <style> #page { height: 100%; @@ -68,10 +70,30 @@ const Template = () => html` iconSrc="chrome://browser/skin/preferences/category-general.svg" > </moz-page-nav-button> + ${when( + hasFooterLinks, + () => html` <moz-page-nav-button + support-page="test" + data-l10n-id="moz-page-nav-button-four" + iconSrc="chrome://browser/skin/preferences/category-general.svg" + slot="secondary-nav" + > + </moz-page-nav-button> + <moz-page-nav-button + href="https://www.example.com" + data-l10n-id="moz-page-nav-button-five" + iconSrc="chrome://browser/skin/preferences/category-general.svg" + slot="secondary-nav" + > + </moz-page-nav-button>` + )} </moz-page-nav> <main></main> </div> `; export const Default = Template.bind({}); -Default.args = {}; +Default.args = { hasFooterLinks: false }; + +export const WithFooterLinks = Template.bind({}); +WithFooterLinks.args = { hasFooterLinks: true }; 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 9d2d6ffac2..df6ebac2a5 100644 --- a/toolkit/content/widgets/moz-support-link/moz-support-link.mjs +++ b/toolkit/content/widgets/moz-support-link/moz-support-link.mjs @@ -29,6 +29,10 @@ export default class MozSupportLink extends HTMLAnchorElement { */ #register() { if (window.document.nodePrincipal?.isSystemPrincipal) { + ChromeUtils.defineESModuleGetters(MozSupportLink, { + BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs", + }); + // eslint-disable-next-line no-shadow let { XPCOMUtils } = window.XPCOMUtils ? window @@ -72,7 +76,7 @@ export default class MozSupportLink extends HTMLAnchorElement { handleEvent(e) { if (e.type == "click") { if (window.openTrustedLinkIn) { - let where = whereToOpenLink(e, false, true); + let where = MozSupportLink.BrowserUtils.whereToOpenLink(e, false, true); if (where == "current") { where = "tab"; } diff --git a/toolkit/content/widgets/notificationbox.js b/toolkit/content/widgets/notificationbox.js index 8cfc7b865c..fc3e553ca4 100644 --- a/toolkit/content/widgets/notificationbox.js +++ b/toolkit/content/widgets/notificationbox.js @@ -621,7 +621,7 @@ customElements.define("notification", MozElements.Notification); async function createNotificationMessageElement() { - await window.ensureCustomElements("moz-message-bar"); + document.createElement("moz-message-bar"); let MozMessageBar = await customElements.whenDefined("moz-message-bar"); class NotificationMessage extends MozMessageBar { static queries = { @@ -772,7 +772,6 @@ let buttonElem; if (button.hasOwnProperty("supportPage")) { - window.ensureCustomElements("moz-support-link"); buttonElem = document.createElement("a", { is: "moz-support-link", }); diff --git a/toolkit/content/widgets/panel-list/panel-list.js b/toolkit/content/widgets/panel-list/panel-list.js index 2e93b4ddc3..a2b6cb1a00 100644 --- a/toolkit/content/widgets/panel-list/panel-list.js +++ b/toolkit/content/widgets/panel-list/panel-list.js @@ -439,10 +439,6 @@ // using the mouse. Ignore the first focusin event if it's on the // triggering target. this.focusHasChanged = true; - } else if (!target || !inPanelList) { - // If the target isn't in the panel, hide. This will close when focus - // moves out of the panel. - this.hide(); } else { // Just record that there was a focusin event. this.focusHasChanged = true; diff --git a/toolkit/content/widgets/popupnotification.js b/toolkit/content/widgets/popupnotification.js index 835151496c..7dfa47728b 100644 --- a/toolkit/content/widgets/popupnotification.js +++ b/toolkit/content/widgets/popupnotification.js @@ -116,8 +116,6 @@ MozXULElement.insertFTLIfNeeded("toolkit/global/popupnotification.ftl"); this.appendChild(this.constructor.fragment); - window.ensureCustomElements("moz-button-group"); - this.button = this.querySelector(".popup-notification-primary-button"); if ( this.hasAttribute("buttonlabel") || diff --git a/toolkit/content/widgets/videocontrols.js b/toolkit/content/widgets/videocontrols.js index 73a32164aa..8d7b52c344 100644 --- a/toolkit/content/widgets/videocontrols.js +++ b/toolkit/content/widgets/videocontrols.js @@ -417,10 +417,9 @@ this.VideoControlsImplWidget = class { } // We have to check again if the media has audio here. - if (!this.isAudioOnly && !this.video.mozHasAudio) { - this.muteButton.setAttribute("noAudio", "true"); - this.muteButton.disabled = true; - } + let noAudio = !this.isAudioOnly && !this.video.mozHasAudio; + this.muteButton.toggleAttribute("noAudio", noAudio); + this.muteButton.disabled = noAudio; } // The video itself might not be fullscreen, but part of the @@ -755,7 +754,7 @@ this.VideoControlsImplWidget = class { ); } break; - case "loadedmetadata": + case "loadedmetadata": { // If a <video> doesn't have any video data, treat it as <audio> // and show the controls (they won't fade back out) if ( @@ -771,13 +770,13 @@ this.VideoControlsImplWidget = class { Math.round(this.video.currentTime * 1000), Math.round(this.video.duration * 1000) ); - if (!this.isAudioOnly && !this.video.mozHasAudio) { - this.muteButton.setAttribute("noAudio", "true"); - this.muteButton.disabled = true; - } + let noAudio = !this.isAudioOnly && !this.video.mozHasAudio; + this.muteButton.toggleAttribute("noAudio", noAudio); + this.muteButton.disabled = noAudio; this.adjustControlSize(); this.updatePictureInPictureToggleDisplay(); break; + } case "durationchange": this.updatePictureInPictureToggleDisplay(); break; |