summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/unifiedtoolbar/content/customization-target.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/unifiedtoolbar/content/customization-target.mjs')
-rw-r--r--comm/mail/components/unifiedtoolbar/content/customization-target.mjs333
1 files changed, 333 insertions, 0 deletions
diff --git a/comm/mail/components/unifiedtoolbar/content/customization-target.mjs b/comm/mail/components/unifiedtoolbar/content/customization-target.mjs
new file mode 100644
index 0000000000..1ea5f67160
--- /dev/null
+++ b/comm/mail/components/unifiedtoolbar/content/customization-target.mjs
@@ -0,0 +1,333 @@
+/* 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 ListBoxSelection from "./list-box-selection.mjs";
+import "./customizable-element.mjs"; // eslint-disable-line import/no-unassigned-import
+
+const { getAvailableItemIdsForSpace } = ChromeUtils.importESModule(
+ "resource:///modules/CustomizableItems.sys.mjs"
+);
+
+/**
+ * Customization target where items can be placed, rearranged and removed.
+ * Attributes:
+ * - aria-label: Name of the target area.
+ * - current-items: Comma separated item IDs currently in this area. When
+ * changed initialize should be called.
+ * Events:
+ * - itemchange: Fired whenever the items inside the toolbar are added, moved or
+ * removed.
+ * - space: The space this target is in.
+ */
+class CustomizationTarget extends ListBoxSelection {
+ contextMenuId = "customizationTargetMenu";
+ actionKey = "Delete";
+ canMoveItems = true;
+
+ connectedCallback() {
+ if (super.connectedCallback()) {
+ return;
+ }
+
+ document
+ .getElementById("customizationTargetForward")
+ .addEventListener("command", this.#handleMenuForward);
+ document
+ .getElementById("customizationTargetBackward")
+ .addEventListener("command", this.#handleMenuBackward);
+ document
+ .getElementById("customizationTargetRemove")
+ .addEventListener("command", this.#handleMenuRemove);
+ document
+ .getElementById("customizationTargetRemoveEverywhere")
+ .addEventListener("command", this.#handleMenuRemoveEverywhere);
+ document
+ .getElementById("customizationTargetAddEverywhere")
+ .addEventListener("command", this.#handleMenuAddEverywhere);
+ document
+ .getElementById("customizationTargetStart")
+ .addEventListener("command", this.#handleMenuStart);
+ document
+ .getElementById("customizationTargetEnd")
+ .addEventListener("command", this.#handleMenuEnd);
+
+ this.initialize();
+ }
+
+ /**
+ * Initialize the contents of the target from the current state. The relevant
+ * state is passed in via the current-items attribute.
+ */
+ initialize() {
+ const itemIds = this.getAttribute("current-items").split(",");
+ this.setItems(itemIds);
+ }
+
+ /**
+ * Update the items in the target from an array of item IDs.
+ *
+ * @param {string[]} itemIds - ordered array of IDs of the items currently in
+ * the target
+ */
+ setItems(itemIds) {
+ const childCount = this.children.length;
+ const availableItems = getAvailableItemIdsForSpace(
+ this.getAttribute("space"),
+ true
+ );
+ this.replaceChildren(
+ ...itemIds.map(itemId => {
+ const element = document.createElement("li", {
+ is: "customizable-element",
+ });
+ element.setAttribute("item-id", itemId);
+ element.setAttribute("disabled", "disabled");
+ element.classList.toggle("collapsed", !availableItems.includes(itemId));
+ element.draggable = true;
+ return element;
+ })
+ );
+ if (childCount) {
+ this.#onChange();
+ }
+ }
+
+ /**
+ * Human-readable name of the customization target area.
+ *
+ * @type {string}
+ */
+ get name() {
+ return this.getAttribute("aria-label");
+ }
+
+ handleContextMenu = event => {
+ this.initializeContextMenu(event);
+ const notForAllSpaces = !this.contextMenuFor.allSpaces;
+ const removeEverywhereItem = document.getElementById(
+ "customizationTargetRemoveEverywhere"
+ );
+ const addEverywhereItem = document.getElementById(
+ "customizationTargetAddEverywhere"
+ );
+ addEverywhereItem.setAttribute("hidden", notForAllSpaces.toString());
+ removeEverywhereItem.setAttribute("hidden", notForAllSpaces.toString());
+ if (!notForAllSpaces) {
+ const customization = this.getRootNode().host.closest(
+ "unified-toolbar-customization"
+ );
+ const itemId = this.contextMenuFor.getAttribute("item-id");
+ addEverywhereItem.disabled =
+ !this.contextMenuFor.allowMultiple &&
+ customization.activeInAllSpaces(itemId);
+ removeEverywhereItem.disabled =
+ this.contextMenuFor.allowMultiple ||
+ !customization.activeInMultipleSpaces(itemId);
+ }
+ const isFirstElement = this.contextMenuFor === this.firstElementChild;
+ const isLastElement = this.contextMenuFor === this.lastElementChild;
+ document.getElementById("customizationTargetBackward").disabled =
+ isFirstElement;
+ document.getElementById("customizationTargetForward").disabled =
+ isLastElement;
+ document.getElementById("customizationTargetStart").disabled =
+ isFirstElement;
+ document.getElementById("customizationTargetEnd").disabled = isLastElement;
+ };
+
+ /**
+ * Event handler when the context menu item to move the item forward is
+ * selected.
+ */
+ #handleMenuForward = () => {
+ if (this.contextMenuFor) {
+ this.moveItemForward(this.contextMenuFor);
+ }
+ };
+
+ /**
+ * Event handler when the context menu item to move the item backward is
+ * selected.
+ */
+ #handleMenuBackward = () => {
+ if (this.contextMenuFor) {
+ this.moveItemBackward(this.contextMenuFor);
+ }
+ };
+
+ /**
+ * Event handler when the context menu item to remove the item is selected.
+ */
+ #handleMenuRemove = () => {
+ if (this.contextMenuFor) {
+ this.primaryAction(this.contextMenuFor);
+ }
+ };
+
+ #handleMenuRemoveEverywhere = () => {
+ if (this.contextMenuFor) {
+ this.primaryAction(this.contextMenuFor);
+ this.dispatchEvent(
+ new CustomEvent("removeitem", {
+ detail: {
+ itemId: this.contextMenuFor.getAttribute("item-id"),
+ },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }
+ };
+
+ #handleMenuAddEverywhere = () => {
+ if (this.contextMenuFor) {
+ this.dispatchEvent(
+ new CustomEvent("additem", {
+ detail: {
+ itemId: this.contextMenuFor.getAttribute("item-id"),
+ },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }
+ };
+
+ #handleMenuStart = () => {
+ if (this.contextMenuFor) {
+ this.moveItemToStart(this.contextMenuFor);
+ }
+ };
+
+ #handleMenuEnd = () => {
+ if (this.contextMenuFor) {
+ this.moveItemToEnd(this.contextMenuFor);
+ }
+ };
+
+ /**
+ * Emit a change event. Should be called whenever items are added, moved or
+ * removed from the target.
+ */
+ #onChange() {
+ const changeEvent = new Event("itemchange", {
+ bubbles: true,
+ // Make sure this bubbles out of the pane shadow root.
+ composed: true,
+ });
+ this.dispatchEvent(changeEvent);
+ }
+
+ /**
+ * Adopt an item from another list into this one.
+ *
+ * @param {?CustomizableElement} item - Item from another list.
+ */
+ #adoptItem(item) {
+ item?.setAttribute("disabled", "disabled");
+ }
+
+ moveItemForward(...args) {
+ super.moveItemForward(...args);
+ this.#onChange();
+ }
+
+ moveItemBackward(...args) {
+ super.moveItemBackward(...args);
+ this.#onChange();
+ }
+
+ moveItemToStart(...args) {
+ super.moveItemToStart(...args);
+ this.#onChange();
+ }
+
+ moveItemToEnd(...args) {
+ super.moveItemToEnd(...args);
+ this.#onChange();
+ }
+
+ handleDrop(itemId, sibling, afterSibling) {
+ const item = super.handleDrop(itemId, sibling, afterSibling);
+ if (item) {
+ this.#adoptItem(item);
+ this.#onChange();
+ }
+ }
+
+ handleDragSuccess(item) {
+ super.handleDragSuccess(item);
+ this.#onChange();
+ }
+
+ /**
+ * Return the item to its palette, removing it from this target.
+ *
+ * @param {CustomizableElement} item - The item to remove.
+ */
+ primaryAction(item) {
+ if (super.primaryAction(item)) {
+ return;
+ }
+ item.palette.returnItem(item);
+ this.#onChange();
+ }
+
+ /**
+ * Add an item to the end of this customization target.
+ *
+ * @param {CustomizableElement} item - The item to add.
+ */
+ addItem(item) {
+ if (!item) {
+ return;
+ }
+ this.#adoptItem(item);
+ this.append(item);
+ this.#onChange();
+ }
+
+ removeItemById(itemId) {
+ const item = this.querySelector(`[item-id="${itemId}"]`);
+ if (!item) {
+ return;
+ }
+ this.primaryAction(item);
+ }
+
+ /**
+ * Check if an item is currently used in this target.
+ *
+ * @param {string} itemId - Item ID of the item to check for.
+ * @returns {boolean} If the item is currently used in this target.
+ */
+ hasItem(itemId) {
+ return Boolean(this.querySelector(`[item-id="${itemId}"]`));
+ }
+
+ /**
+ * IDs of the items currently in this target, in correct order including
+ * duplicates.
+ *
+ * @type {string[]}
+ */
+ get itemIds() {
+ return Array.from(this.children, element =>
+ element.getAttribute("item-id")
+ );
+ }
+
+ /**
+ * If the contents of this target differ from the currently saved
+ * configuration.
+ *
+ * @type {boolean}
+ */
+ get hasChanges() {
+ return this.itemIds.join(",") !== this.getAttribute("current-items");
+ }
+}
+customElements.define("customization-target", CustomizationTarget, {
+ extends: "ul",
+});