diff --git a/toolkit/content/customElements.js b/toolkit/content/customElements.js index 3735ffe9b465..d6b6ac791a62 100644 --- a/toolkit/content/customElements.js +++ b/toolkit/content/customElements.js @@ -874,6 +874,14 @@ "chrome://global/content/elements/moz-support-link.mjs", ], ["moz-toggle", "chrome://global/content/elements/moz-toggle.mjs"], + [ + "moz-visual-picker", + "chrome://global/content/elements/moz-visual-picker.mjs", + ], + [ + "moz-visual-picker-item", + "chrome://global/content/elements/moz-visual-picker.mjs", + ], ]) { if (!customElements.get(tag)) { customElements.setElementCreationCallback( diff --git a/toolkit/content/jar.mn b/toolkit/content/jar.mn index 94caf8642b88..d2a70e7d3cf7 100644 --- a/toolkit/content/jar.mn +++ b/toolkit/content/jar.mn @@ -132,6 +132,8 @@ toolkit.jar: content/global/elements/moz-support-link.mjs (widgets/moz-support-link/moz-support-link.mjs) content/global/elements/moz-toggle.css (widgets/moz-toggle/moz-toggle.css) content/global/elements/moz-toggle.mjs (widgets/moz-toggle/moz-toggle.mjs) + content/global/elements/moz-visual-picker-item.css (widgets/moz-visual-picker/moz-visual-picker-item.css) + content/global/elements/moz-visual-picker.mjs (widgets/moz-visual-picker/moz-visual-picker.mjs) content/global/elements/named-deck.js (widgets/named-deck.js) content/global/elements/infobar.css (widgets/infobar.css) content/global/elements/notificationbox.js (widgets/notificationbox.js) diff --git a/toolkit/content/tests/widgets/chrome.toml b/toolkit/content/tests/widgets/chrome.toml index 2d6038b69e10..01accb5d9a21 100644 --- a/toolkit/content/tests/widgets/chrome.toml +++ b/toolkit/content/tests/widgets/chrome.toml @@ -72,6 +72,8 @@ skip-if = [ ["test_moz_toggle.html"] +["test_moz_visual_picker.html"] + ["test_panel_item_accesskey.html"] ["test_panel_item_checkbox.html"] diff --git a/toolkit/content/tests/widgets/lit-test-helpers.js b/toolkit/content/tests/widgets/lit-test-helpers.js index 395032f09ce3..ef425076a5cd 100644 --- a/toolkit/content/tests/widgets/lit-test-helpers.js +++ b/toolkit/content/tests/widgets/lit-test-helpers.js @@ -115,12 +115,12 @@ class InputTestHelpers extends LitTestHelpers { let { activatedProperty } = this; function trackEvent(event) { - let reactiveProps = event.target.constructor.properties; + let reactiveProps = event.target.constructor?.properties; seenEvents.push({ type: event.type, - value: event.target.value, + value: event.currentTarget.value, localName: event.currentTarget.localName, - ...(reactiveProps.hasOwnProperty(activatedProperty) && { + ...(reactiveProps?.hasOwnProperty(activatedProperty) && { [activatedProperty]: event.target[activatedProperty], }), }); diff --git a/toolkit/content/tests/widgets/test_moz_visual_picker.html b/toolkit/content/tests/widgets/test_moz_visual_picker.html new file mode 100644 index 000000000000..c74d411441c9 --- /dev/null +++ b/toolkit/content/tests/widgets/test_moz_visual_picker.html @@ -0,0 +1,491 @@ + + + + + MozVisualPicker Tests + + + + + + + + + + +

+ +

+  
+
diff --git a/toolkit/content/widgets/moz-radio-group/moz-radio-group.mjs b/toolkit/content/widgets/moz-radio-group/moz-radio-group.mjs
index 2b9be2e0a454..642371008c5a 100644
--- a/toolkit/content/widgets/moz-radio-group/moz-radio-group.mjs
+++ b/toolkit/content/widgets/moz-radio-group/moz-radio-group.mjs
@@ -54,6 +54,8 @@ export class MozRadioGroup extends MozLitElement {
   #radioButtons;
   #value;
 
+  static childElementName = "moz-radio";
+
   static properties = {
     disabled: { type: Boolean, reflect: true },
     description: { type: String, fluent: true },
@@ -100,7 +102,9 @@ export class MozRadioGroup extends MozLitElement {
         this.shadowRoot
           ?.querySelector("slot:not([name])")
           ?.assignedElements() || [...this.children]
-      )?.filter(el => el.localName === "moz-radio" && !el.slot);
+      )?.filter(
+        el => el.localName === this.constructor.childElementName && !el.slot
+      );
       this.#radioButtons.forEach(button => customElements.upgrade(button));
     }
     return this.#radioButtons;
@@ -277,6 +281,15 @@ export class MozRadio extends MozBaseInputElement {
     return this.#controller;
   }
 
+  get isDisabled() {
+    return (
+      this.disabled ||
+      this.#controller.disabled ||
+      this.parentDisabled ||
+      this.#controller.parentDisabled
+    );
+  }
+
   constructor() {
     super();
     this.checked = false;
@@ -330,8 +343,12 @@ export class MozRadio extends MozBaseInputElement {
   }
 
   handleClick() {
+    if (this.isDisabled || this.checked) {
+      return;
+    }
+
     this.#controller.value = this.value;
-    if (this.getRootNode().activeElement?.localName == "moz-radio") {
+    if (this.getRootNode().activeElement?.localName == this.localName) {
       this.focus();
     }
   }
@@ -342,11 +359,6 @@ export class MozRadio extends MozBaseInputElement {
   }
 
   inputTemplate() {
-    let isDisabled =
-      this.disabled ||
-      this.#controller.disabled ||
-      this.parentDisabled ||
-      this.#controller.parentDisabled;
     return html`
+      
+    `;
+  }
+}
+customElements.define("moz-visual-picker-item", MozVisualPickerItem);
diff --git a/toolkit/content/widgets/moz-visual-picker/moz-visual-picker.stories.mjs b/toolkit/content/widgets/moz-visual-picker/moz-visual-picker.stories.mjs
new file mode 100644
index 000000000000..05c7b8e76277
--- /dev/null
+++ b/toolkit/content/widgets/moz-visual-picker/moz-visual-picker.stories.mjs
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { html, ifDefined, classMap } from "../vendor/lit.all.mjs";
+import "./moz-visual-picker.mjs";
+
+export default {
+  title: "UI Widgets/Visual Picker",
+  component: "moz-visual-picker",
+  argTypes: {
+    value: {
+      options: ["1", "2", "3"],
+      control: { type: "select" },
+    },
+    slottedItem: {
+      options: ["card", "avatar"],
+      control: { type: "select" },
+    },
+    pickerL10nId: {
+      options: ["moz-visual-picker", "moz-visual-picker-description"],
+      control: { type: "select" },
+    },
+  },
+  parameters: {
+    actions: {
+      handles: ["click", "input", "change"],
+    },
+    status: "in-development",
+    fluent: `
+moz-visual-picker =
+  .label = Pick something
+moz-visual-picker-description =
+  .label = Pick something
+  .description = Pick one of these cool things please
+`,
+  },
+};
+
+const AVATAR_ICONS = [
+  "chrome://global/skin/icons/defaultFavicon.svg",
+  "chrome://global/skin/icons/experiments.svg",
+  "chrome://global/skin/icons/heart.svg",
+];
+
+function getSlottedContent(type, index) {
+  if (type == "card") {
+    return html`
+ + I'm card number ${index + 1} +
`; + } + return html`
+ +
`; +} + +const Template = ({ value, slottedItem, pickerL10nId, supportPage }) => { + return html` + + + ${[...Array.from({ length: 3 })].map( + (_, i) => + html` + ${getSlottedContent(slottedItem, i)} + ` + )} + + `; +}; + +export const Default = Template.bind({}); +Default.args = { + pickerL10nId: "moz-visual-picker", + slottedItem: "card", + value: "1", + supportPage: "", +}; + +export const WithPickerDescription = Template.bind({}); +WithPickerDescription.args = { + ...Default.args, + pickerL10nId: "moz-visual-picker-description", +}; + +export const WithPickerSupportLink = Template.bind({}); +WithPickerSupportLink.args = { + ...WithPickerDescription.args, + supportPage: "foo", +}; + +export const AllUnselected = Template.bind({}); +AllUnselected.args = { + ...Default.args, + value: "", +};