From 9681aa98956129e80526e081a089eafcb1d86a72 Mon Sep 17 00:00:00 2001 From: EnderDev Date: Sat, 30 Dec 2023 16:28:14 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=84=20Add=20styling=20for=20XUL=20menu?= =?UTF-8?q?popup=20and=20menuitems?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/panels/content/xul-menu.js | 9 ++ components/panels/content/xul-menuitem.css | 41 ++++++++ components/panels/content/xul-menuitem.js | 71 +++++++++++++ components/panels/content/xul-menupopup.css | 76 ++++++++++++++ components/panels/content/xul-menupopup.js | 110 ++++++++++++++++++++ components/panels/jar.mn | 15 +++ themes/shared/xul/input.css | 49 +++++++-- 7 files changed, 361 insertions(+), 10 deletions(-) create mode 100644 components/panels/content/xul-menu.js create mode 100644 components/panels/content/xul-menuitem.css create mode 100644 components/panels/content/xul-menuitem.js create mode 100644 components/panels/content/xul-menupopup.css create mode 100644 components/panels/content/xul-menupopup.js diff --git a/components/panels/content/xul-menu.js b/components/panels/content/xul-menu.js new file mode 100644 index 0000000000..b76d57db02 --- /dev/null +++ b/components/panels/content/xul-menu.js @@ -0,0 +1,9 @@ +/* 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/. */ + +{ + Services.scriptloader.loadSubScript( + "chrome://dot/content/widgets/xul-menuitem.js" + ); +} diff --git a/components/panels/content/xul-menuitem.css b/components/panels/content/xul-menuitem.css new file mode 100644 index 0000000000..2911903241 --- /dev/null +++ b/components/panels/content/xul-menuitem.css @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@import url("chrome://dot/skin/input.css"); +@import url("chrome://dot/content/widgets/browser-panel-button.css"); + +::slotted(menuitem), +::slotted(menu) { + --button-default-color: transparent !important; + + padding: 6px 30px; +} + +::slotted(menu) { + background-image: url(chrome://dot/skin/chevron-right.svg); + + -moz-context-properties: fill, fill-opacity; + fill: currentColor; + + background-repeat: no-repeat; + background-position: calc(100% - 10px) center; +} + +::slotted(menuitem[type=checkbox][checked]) { + background-image: url(chrome://dot/skin/check.svg); + + -moz-context-properties: fill, fill-opacity; + fill: currentColor; + + background-repeat: no-repeat; + background-position: 10px center; +} + +::slotted(menuseparator) { + width: calc(100% - 4px * 2); + height: 1px; + margin-block: 2px; + margin-inline: 4px; + background: linear-gradient(to top, var(--arrowpanel-border-color), var(--arrowpanel-border-color)), color-mix(in srgb, currentColor 15%, transparent 100%); +} \ No newline at end of file diff --git a/components/panels/content/xul-menuitem.js b/components/panels/content/xul-menuitem.js new file mode 100644 index 0000000000..0d710e89d3 --- /dev/null +++ b/components/panels/content/xul-menuitem.js @@ -0,0 +1,71 @@ +/* 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/. */ + +{ + class MozMenuItem extends MozXULElement { + /** + * The allowed customizable attributes for the menu item + */ + static get customizableAttributes() { + return { + type: (value) => { + if (!["normal", "group", "separator"].includes(value)) { + throw new Error( + `Attribute 'type' must be either "normal", "separator" or "group", got '${value}'.` + ); + } + + return value; + }, + + orientation: "orientation" + }; + } + + /** + * Determines what type of menu item this is + */ + get type() { + return this.getAttribute("type"); + } + + /** + * Determines the orientation of the menu item + */ + get orientation() { + return this.getAttribute("orientation"); + } + + set orientation(newValue) { + this.setAttribute("orientation", newValue); + } + + /** + * Determines the icons mode of the menu item + */ + get mode() { + return this.getAttribute("mode"); + } + + set mode(newValue) { + this.setAttribute("mode", newValue); + } + + connectedCallback() { + if (this.delayConnectedCallback()) return; + + if (this.type == "group") { + if (!this.getAttribute("orientation")) { + this.orientation = "horizontal"; + } + } + + if (this.orientation == "horizontal") { + this.mode = "icons"; + } + } + } + + customElements.define("menuitem", MozMenuItem); +} diff --git a/components/panels/content/xul-menupopup.css b/components/panels/content/xul-menupopup.css new file mode 100644 index 0000000000..bfed085b8a --- /dev/null +++ b/components/panels/content/xul-menupopup.css @@ -0,0 +1,76 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +@import url("chrome://dot/content/widgets/xul-menuitem.css"); + +:host(menupopup) { + --panel-shadow-margin: 10px; + + -moz-window-input-region-margin: var(--panel-shadow-margin); + margin: calc(-1 * (var(--panel-shadow-margin) + var(--panel-inner-padding, 0px))); + + -moz-window-dragging: no-drag; + + transition: 0.2s opacity cubic-bezier(0.19, 1, 0.22, 1); +} + +:host(menupopup[animate="true"]) { + opacity: 0; +} + +:host(menupopup[animate="true"][open]) { + opacity: 1; +} + +:host(menupopup:not([open])) { + pointer-events: none; +} + +.browser-panel-container { + display: flex; + flex-direction: column; + + width: auto; + height: auto; + + min-width: 16px; + min-height: 16px; + + padding: 2px; + margin: var(--panel-shadow-margin); + + background-color: var(--arrowpanel-background); + + border: 1px solid var(--arrowpanel-border-color); + border-radius: 8px; + + box-shadow: 0 3.2px 7.2px 0 rgba(0, 0, 0, .132), 0 .6px 1.8px 0 rgba(0, 0, 0, .108); + + overflow: clip; + + gap: 2px; +} + +:host(.browser-panel-container) .browser-panel-header { + display: flex; + + justify-content: center; + align-items: center; + + width: 100%; + padding: 14px 0px; + + border-bottom: 1px solid color-mix(in srgb, currentColor 15%, transparent 100%); + + & .browser-panel-header-title { + font-weight: bold; + } +} + +:host(.browser-panel-container) .browser-panel-content { + display: flex; + + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/components/panels/content/xul-menupopup.js b/components/panels/content/xul-menupopup.js new file mode 100644 index 0000000000..e6c58549ef --- /dev/null +++ b/components/panels/content/xul-menupopup.js @@ -0,0 +1,110 @@ +/* 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/. */ + +// @ts-nocheck + +"use strict"; + +// This is loaded into all XUL windows. Wrap in a block to prevent +// leaking to window scope. +{ + class MozMenuPopup extends MozElements.MozElementMixin(XULPopupElement) { + constructor() { + super(); + + this.attachShadow({ mode: "open" }); + } + + /** + * The container element that holds the panel's contents + */ + get container() { + return /** @type {HTMLElement} */ ( + this.shadowRoot.querySelector(".browser-panel-container") + ); + } + + /** + * Fires when the popup starts showing on-screen + * @param {Event} event + */ + onPopupShowing(event) { + if (event.target != this) return; + + this.ensureInitialized(); + } + + /** + * Fires when the popup is showing + * @param {Event} event + */ + onPopupShown(event) { + this.setAttribute("open", ""); + } + + /** + * Fires when the popup starts being hidden + */ + onPopupHiding() { + this.removeAttribute("open"); + + // Remove the panel once all transitions have completed + if (this.getAttribute("animate") == "true") { + this.addEventListener( + "transitionend", + () => { + this.hidePopup(); + }, + { + once: true + } + ); + } else { + this.hidePopup(); + } + } + + connectedCallback() { + if (this.delayConnectedCallback() || this.hasConnected) { + return; + } + + this.setAttribute("animate", "true"); + this.setAttribute("consumeoutsideclicks", "true"); + this.setAttribute("incontentshell", "false"); + + this.addEventListener( + "popupshowing", + this.onPopupShowing.bind(this) + ); + + this.addEventListener("popupshown", this.onPopupShown.bind(this)); + + this.addEventListener("popuphiding", this.onPopupHiding.bind(this)); + + this.hasConnected = true; + } + + ensureInitialized() { + if (this.shadowRoot.firstChild) return; + + const style = document.createElement("link"); + style.rel = "stylesheet"; + style.href = "chrome://dot/content/widgets/xul-menupopup.css"; + + const container = document.createElement("div"); + container.classList.add("browser-panel-container"); + + const slot = document.createElement("slot"); + + container.appendChild(slot); + + super.shadowRoot.append(style, container); + } + } + + customElements.define("menupopup", MozMenuPopup); + + MozElements.MozMenuPopup = MozMenuPopup; +} diff --git a/components/panels/jar.mn b/components/panels/jar.mn index b55de6a012..487287c185 100644 --- a/components/panels/jar.mn +++ b/components/panels/jar.mn @@ -23,3 +23,18 @@ dot.jar: content/widgets/browser-panel-button.js (content/browser-panel-button.js) content/widgets/browser-panel-button.css (content/browser-panel-button.css) + + content/widgets/xul-menu.js (content/xul-menu.js) + + content/widgets/xul-menuitem.js (content/xul-menuitem.js) + content/widgets/xul-menuitem.css (content/xul-menuitem.css) + + content/widgets/xul-menupopup.js (content/xul-menupopup.js) + content/widgets/xul-menupopup.css (content/xul-menupopup.css) + +% override chrome://global/content/menu.js chrome://dot/content/widgets/xul-menu.js +% override chrome://global/content/elements/menupopup.js chrome://dot/content/widgets/xul-menupopup.js + +% override chrome://global/skin/popup.css chrome://dot/content/compat/blank.css +% override chrome://global/skin/menu.css chrome://dot/content/compat/blank.css +% override chrome://global/skin/menu-shared.css chrome://dot/content/compat/blank.css \ No newline at end of file diff --git a/themes/shared/xul/input.css b/themes/shared/xul/input.css index faff0b7781..f35eeda10d 100644 --- a/themes/shared/xul/input.css +++ b/themes/shared/xul/input.css @@ -9,7 +9,9 @@ button, select, xul|button, html|button, -html|select { +html|select, +::slotted(menuitem), +::slotted(menu) { --button-disabled-opacity: 0.5; --button-base-color: currentColor; @@ -38,7 +40,9 @@ button, select, xul|button, html|button, -html|select { +html|select, +::slotted(menuitem), +::slotted(menu) { appearance: none; min-height: 24px; @@ -67,7 +71,9 @@ html|select { button, xul|button, -html|button { +html|button, +::slotted(menuitem), +::slotted(menu) { padding: 6px 24px; } @@ -87,18 +93,33 @@ select:focus:not(:disabled), xul|button:hover:not(:disabled), html|button:hover:not(:disabled), html|select:hover:not(:disabled), -html|select:focus:not(:disabled) { +html|select:focus:not(:disabled), +::slotted(menuitem[_moz-menuactive]:not([disabled])) { --button-background: var(--button-hover-color); } +/* + * Since we are applying the background using CSS variables, + * it would change the background of children in menu elements, + * which is why we are simply applying the hover bg straight up. + */ +::slotted(menu[_moz-menuactive]:not([disabled])) { + background-color: var(--button-hover-color) !important; +} + button:is(:hover, :focus-visible):is(:active, [active], [mouseactive]):not(:disabled), select:is(:focus-visible):is(:active, [active], [mouseactive]):not(:disabled), xul|button:is(:hover, :focus-visible):is(:active, [active], [mouseactive]):not(:disabled), html|button:is(:hover, :focus-visible):is(:active, [active], [mouseactive]):not(:disabled), -html|select:is(:focus-visible):is(:active, [active], [mouseactive]):not(:disabled) { +html|select:is(:focus-visible):is(:active, [active], [mouseactive]):not(:disabled), +::slotted(menuitem:is([_moz-menuactive], :focus-visible):is(:active, [active], [mouseactive]):not([disabled])) { --button-background: var(--button-active-color); } +::slotted(menu:is([_moz-menuactive], :focus-visible):is(:active, [active], [mouseactive]):not([disabled])) { + background-color: var(--button-active-color) !important; +} + button:disabled>*, select:disabled, xul|button:disabled>*, @@ -108,15 +129,19 @@ button[disabled], select[disabled], xul|button[disabled], html|button[disabled], -html|select[disabled] { +html|select[disabled], +::slotted(menuitem[disabled]), +::slotted(menu[disabled]) { filter: opacity(var(--button-disabled-opacity)); + pointer-events: none; } button:focus-visible:not(:is(:active, [active], [mouseactive])), select:focus-visible, xul|button:focus-visible:not(:is(:active, [active], [mouseactive])), html|button:focus-visible:not(:is(:active, [active], [mouseactive])), -html|select:focus-visible { +html|select:focus-visible, +::slotted(menuitem:focus-visible:not(:is(:active, [active], [mouseactive]))) { --button-background: var(--button-hover-color); --button-outline: 2px solid AccentColor; @@ -127,7 +152,8 @@ html|select:focus-visible { button:focus-visible:is(:active, [active], [mouseactive]), xul|button:focus-visible:is(:active, [active], [mouseactive]), -html|button:focus-visible:is(:active, [active], [mouseactive]) { +html|button:focus-visible:is(:active, [active], [mouseactive]), +::slotted(menuitem:focus-visible:is(:active, [active], [mouseactive])) { --button-outline: 2px solid AccentColor; --button-outline-offset: 0px; @@ -136,7 +162,8 @@ html|button:focus-visible:is(:active, [active], [mouseactive]) { button[primary], xul|button[primary], -html|button[primary] { +html|button[primary], +::slotted(menuitem[primary]) { --button-base-color: AccentColor; --button-mix-color: currentColor; --button-color: white; @@ -155,7 +182,9 @@ html|button[primary] { select:not(:disabled), xul|button:not(:disabled), html|button:not(:disabled), - html|select:not(:disabled) { + html|select:not(:disabled), + ::slotted(menuitem:not([disabled])), + ::slotted(menu:not([disabled])) { outline: 1px solid var(--browser-high-contrast-color); } } \ No newline at end of file