tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

setting-element.mjs (5109B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 import {
      6  Directive,
      7  noChange,
      8  nothing,
      9  directive,
     10 } from "chrome://global/content/vendor/lit.all.mjs";
     11 
     12 import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
     13 
     14 /** @import { AttributePart } from "chrome://global/content/vendor/lit.all.mjs" */
     15 
     16 /**
     17 * @typedef {object} SettingElementConfig
     18 * @property {string} [id] - The ID for the Setting, this should match the layout id
     19 * @property {string} [l10nId] - The Fluent l10n ID for the setting
     20 * @property {Record<string, string>} [l10nArgs] - An object containing l10n IDs and their values that will be translated with Fluent
     21 * @property {Record<string, any>} [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
     22 * @property {string} [iconSrc] - A path to the icon for the control (if the control supports one)
     23 * @property {string} [slot] - The named slot for the control
     24 * @property {string} [supportPage] - The SUMO support page slug for the setting
     25 * @property {string} [subcategory] - The sub-category slug used for direct linking to a setting from SUMO
     26 */
     27 
     28 /**
     29 * A Lit directive that applies all properties of an object to a DOM element.
     30 *
     31 * This directive interprets keys in the provided props object as follows:
     32 * - Keys starting with `?` set or remove boolean attributes using `toggleAttribute`.
     33 * - Keys starting with `.` set properties directly on the element.
     34 * - Keys starting with `@` are currently not supported and will throw an error.
     35 * - All other keys are applied as regular attributes using `setAttribute`.
     36 *
     37 * It avoids reapplying values that have not changed, but does not currently
     38 * remove properties that were previously set and are no longer present in the new input.
     39 *
     40 * This directive is useful to "spread" an object of attributes/properties declaratively onto an
     41 * element in a Lit template.
     42 */
     43 class SpreadDirective extends Directive {
     44  /**
     45   * A record of previously applied properties to avoid redundant updates.
     46   *
     47   * @type {Record<string, unknown>}
     48   */
     49  #prevProps = {};
     50 
     51  /**
     52   * Render nothing by default as all changes are made in update using DOM APIs
     53   * on the element directly.
     54   *
     55   * @param {Record<string, unknown>} props The props to apply to this element.
     56   */
     57  // eslint-disable-next-line no-unused-vars
     58  render(props) {
     59    return nothing;
     60  }
     61 
     62  /**
     63   * Apply props to the element using DOM APIs, updating only changed values.
     64   *
     65   * @param {AttributePart} part - The part of the template this directive is bound to.
     66   * @param {[Record<string, unknown>]} propsArray - An array with a single object containing props to apply.
     67   * @returns {typeof noChange} - Indicates to Lit that no re-render is needed.
     68   */
     69  update(part, [props]) {
     70    // TODO: This doesn't clear any values that were set in previous calls if
     71    // they are no longer present.
     72    // It isn't entirely clear to me (mstriemer) what we should do if a prop is
     73    // removed, or if the prop has changed from say ?foo to foo. By not
     74    // implementing the auto-clearing hopefully the consumer will do something
     75    // that fits their use case.
     76 
     77    let el = part.element;
     78 
     79    for (let [key, value] of Object.entries(props)) {
     80      // Skip if the value hasn't changed since the last update.
     81      if (value === this.#prevProps[key]) {
     82        continue;
     83      }
     84 
     85      // Update the element based on the property key matching Lit's templates:
     86      //   ?key -> el.toggleAttribute(key, value)
     87      //   .key -> el.key = value
     88      //   key -> el.setAttribute(key, value)
     89      if (key.startsWith("?")) {
     90        el.toggleAttribute(key.slice(1), Boolean(value));
     91      } else if (key.startsWith(".")) {
     92        // @ts-ignore
     93        el[key.slice(1)] = value;
     94      } else if (key.startsWith("@")) {
     95        throw new Error(
     96          `Event listeners are not yet supported with spread (${key})`
     97        );
     98      } else {
     99        el.setAttribute(key, String(value));
    100      }
    101    }
    102 
    103    // Save current props for comparison in the next update.
    104    this.#prevProps = props;
    105 
    106    return noChange;
    107  }
    108 }
    109 
    110 export const spread = directive(SpreadDirective);
    111 
    112 export class SettingElement extends MozLitElement {
    113  /**
    114   * The default properties that the setting element accepts.
    115   *
    116   * @param {SettingElementConfig} config
    117   */
    118  getCommonPropertyMapping(config) {
    119    return {
    120      id: config.id,
    121      "data-l10n-id": config.l10nId ? config.l10nId : undefined,
    122      "data-l10n-args": config.l10nArgs
    123        ? JSON.stringify(config.l10nArgs)
    124        : undefined,
    125      ".iconSrc": config.iconSrc,
    126      "data-subcategory": config.subcategory,
    127      ".supportPage":
    128        config.supportPage != undefined ? config.supportPage : undefined,
    129      slot: config.slot,
    130      ...config.controlAttrs,
    131    };
    132  }
    133 }