/* 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 { Directive, noChange, nothing, directive, } from "chrome://global/content/vendor/lit.all.mjs"; import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; /** @import { AttributePart } from "chrome://global/content/vendor/lit.all.mjs" */ /** * @typedef {object} SettingElementConfig * @property {string} [id] - The ID for the Setting, this should match the layout id * @property {string} [l10nId] - The Fluent l10n ID for the setting * @property {Record} [l10nArgs] - An object containing l10n IDs and their values that will be translated with Fluent * @property {Record} [controlAttrs] - An object of additional attributes to be set on the control. These can be used to further customize the control for example a message bar of the warning type, or what dialog a button should open * @property {string} [iconSrc] - A path to the icon for the control (if the control supports one) * @property {string} [slot] - The named slot for the control * @property {string} [supportPage] - The SUMO support page slug for the setting * @property {string} [subcategory] - The sub-category slug used for direct linking to a setting from SUMO */ /** * A Lit directive that applies all properties of an object to a DOM element. * * This directive interprets keys in the provided props object as follows: * - Keys starting with `?` set or remove boolean attributes using `toggleAttribute`. * - Keys starting with `.` set properties directly on the element. * - Keys starting with `@` are currently not supported and will throw an error. * - All other keys are applied as regular attributes using `setAttribute`. * * It avoids reapplying values that have not changed, but does not currently * remove properties that were previously set and are no longer present in the new input. * * This directive is useful to "spread" an object of attributes/properties declaratively onto an * element in a Lit template. */ class SpreadDirective extends Directive { /** * A record of previously applied properties to avoid redundant updates. * * @type {Record} */ #prevProps = {}; /** * Render nothing by default as all changes are made in update using DOM APIs * on the element directly. * * @param {Record} props The props to apply to this element. */ // eslint-disable-next-line no-unused-vars render(props) { return nothing; } /** * Apply props to the element using DOM APIs, updating only changed values. * * @param {AttributePart} part - The part of the template this directive is bound to. * @param {[Record]} propsArray - An array with a single object containing props to apply. * @returns {typeof noChange} - Indicates to Lit that no re-render is needed. */ update(part, [props]) { // TODO: This doesn't clear any values that were set in previous calls if // they are no longer present. // It isn't entirely clear to me (mstriemer) what we should do if a prop is // removed, or if the prop has changed from say ?foo to foo. By not // implementing the auto-clearing hopefully the consumer will do something // that fits their use case. let el = part.element; for (let [key, value] of Object.entries(props)) { // Skip if the value hasn't changed since the last update. if (value === this.#prevProps[key]) { continue; } // Update the element based on the property key matching Lit's templates: // ?key -> el.toggleAttribute(key, value) // .key -> el.key = value // key -> el.setAttribute(key, value) if (key.startsWith("?")) { el.toggleAttribute(key.slice(1), Boolean(value)); } else if (key.startsWith(".")) { // @ts-ignore el[key.slice(1)] = value; } else if (key.startsWith("@")) { throw new Error( `Event listeners are not yet supported with spread (${key})` ); } else { el.setAttribute(key, String(value)); } } // Save current props for comparison in the next update. this.#prevProps = props; return noChange; } } export const spread = directive(SpreadDirective); export class SettingElement extends MozLitElement { /** * The default properties that the setting element accepts. * * @param {SettingElementConfig} config */ getCommonPropertyMapping(config) { return { id: config.id, "data-l10n-id": config.l10nId ? config.l10nId : undefined, "data-l10n-args": config.l10nArgs ? JSON.stringify(config.l10nArgs) : undefined, ".iconSrc": config.iconSrc, "data-subcategory": config.subcategory, ".supportPage": config.supportPage != undefined ? config.supportPage : undefined, slot: config.slot, ...config.controlAttrs, }; } }