From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../content/customization-target.mjs | 333 +++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 comm/mail/components/unifiedtoolbar/content/customization-target.mjs (limited to 'comm/mail/components/unifiedtoolbar/content/customization-target.mjs') 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", +}); -- cgit v1.2.3