Greasy Fork is available in English.

UserUtils

Various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, manage persistent user configurations, modify the DOM more easily, useful math and array functions and more

لا ينبغي أن لا يتم تثبيت هذا السكريت مباشرة. هو مكتبة لسكبتات لتشمل مع التوجيه الفوقية // @require https://update.greasyfork.org/scripts/472956/1355459/UserUtils.js

// ==UserScript==
// @namespace    https://github.com/Sv443-Network/UserUtils
// @exclude      *
// @author       Sv443
// @supportURL   https://github.com/Sv443-Network/UserUtils/issues
// @homepageURL  https://github.com/Sv443-Network/UserUtils

// ==UserLibrary==
// @name         UserUtils
// @description  Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and more
// @version      6.3.0
// @license      MIT
// @copyright    Sv443 (https://github.com/Sv443)

// ==/UserScript==
// ==/UserLibrary==

// ==OpenUserJS==
// @author       Sv443
// ==/OpenUserJS==

var UserUtils = (function (exports) {
  var __defProp = Object.defineProperty;
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
  var __hasOwnProp = Object.prototype.hasOwnProperty;
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  var __spreadValues = (a, b) => {
    for (var prop in b || (b = {}))
      if (__hasOwnProp.call(b, prop))
        __defNormalProp(a, prop, b[prop]);
    if (__getOwnPropSymbols)
      for (var prop of __getOwnPropSymbols(b)) {
        if (__propIsEnum.call(b, prop))
          __defNormalProp(a, prop, b[prop]);
      }
    return a;
  };
  var __objRest = (source, exclude) => {
    var target = {};
    for (var prop in source)
      if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
        target[prop] = source[prop];
    if (source != null && __getOwnPropSymbols)
      for (var prop of __getOwnPropSymbols(source)) {
        if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
          target[prop] = source[prop];
      }
    return target;
  };
  var __publicField = (obj, key, value) => {
    __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
    return value;
  };
  var __async = (__this, __arguments, generator) => {
    return new Promise((resolve, reject) => {
      var fulfilled = (value) => {
        try {
          step(generator.next(value));
        } catch (e) {
          reject(e);
        }
      };
      var rejected = (value) => {
        try {
          step(generator.throw(value));
        } catch (e) {
          reject(e);
        }
      };
      var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
      step((generator = generator.apply(__this, __arguments)).next());
    });
  };

  // lib/math.ts
  function clamp(value, min, max) {
    return Math.max(Math.min(value, max), min);
  }
  function mapRange(value, range1min, range1max, range2min, range2max) {
    if (Number(range1min) === 0 && Number(range2min) === 0)
      return value * (range2max / range1max);
    return (value - range1min) * ((range2max - range2min) / (range1max - range1min)) + range2min;
  }
  function randRange(...args) {
    let min, max;
    if (typeof args[0] === "number" && typeof args[1] === "number")
      [min, max] = args;
    else if (typeof args[0] === "number" && typeof args[1] !== "number") {
      min = 0;
      [max] = args;
    } else
      throw new TypeError(`Wrong parameter(s) provided - expected: "number" and "number|undefined", got: "${typeof args[0]}" and "${typeof args[1]}"`);
    min = Number(min);
    max = Number(max);
    if (isNaN(min) || isNaN(max))
      return NaN;
    if (min > max)
      throw new TypeError(`Parameter "min" can't be bigger than "max"`);
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }
  function randomId(length = 16, radix = 16) {
    const arr = new Uint8Array(length);
    crypto.getRandomValues(arr);
    return Array.from(
      arr,
      (v) => mapRange(v, 0, 255, 0, radix).toString(radix).substring(0, 1)
    ).join("");
  }

  // lib/array.ts
  function randomItem(array) {
    return randomItemIndex(array)[0];
  }
  function randomItemIndex(array) {
    if (array.length === 0)
      return [void 0, void 0];
    const idx = randRange(array.length - 1);
    return [array[idx], idx];
  }
  function takeRandomItem(arr) {
    const [itm, idx] = randomItemIndex(arr);
    if (idx === void 0)
      return void 0;
    arr.splice(idx, 1);
    return itm;
  }
  function randomizeArray(array) {
    const retArray = [...array];
    if (array.length === 0)
      return retArray;
    for (let i = retArray.length - 1; i > 0; i--) {
      const j = Math.floor(randRange(0, 1e4) / 1e4 * (i + 1));
      [retArray[i], retArray[j]] = [retArray[j], retArray[i]];
    }
    return retArray;
  }

  // lib/DataStore.ts
  var DataStore = class {
    /**
     * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.  
     * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.  
     *   
     * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue`  
     * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
     * 
     * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `options.defaultData`) - this should also be the type of the data format associated with the current `options.formatVersion`
     * @param options The options for this DataStore instance
    */
    constructor(options) {
      __publicField(this, "id");
      __publicField(this, "formatVersion");
      __publicField(this, "defaultData");
      __publicField(this, "cachedData");
      __publicField(this, "migrations");
      __publicField(this, "encodeData");
      __publicField(this, "decodeData");
      this.id = options.id;
      this.formatVersion = options.formatVersion;
      this.defaultData = options.defaultData;
      this.cachedData = options.defaultData;
      this.migrations = options.migrations;
      this.encodeData = options.encodeData;
      this.decodeData = options.decodeData;
    }
    /**
     * Loads the data saved in persistent storage into the in-memory cache and also returns it.  
     * Automatically populates persistent storage with default data if it doesn't contain any data yet.  
     * Also runs all necessary migration functions if the data format has changed since the last time the data was saved.
     */
    loadData() {
      return __async(this, null, function* () {
        try {
          const gmData = yield GM.getValue(`_uucfg-${this.id}`, this.defaultData);
          let gmFmtVer = Number(yield GM.getValue(`_uucfgver-${this.id}`));
          if (typeof gmData !== "string") {
            yield this.saveDefaultData();
            return __spreadValues({}, this.defaultData);
          }
          const isEncoded = yield GM.getValue(`_uucfgenc-${this.id}`, false);
          if (isNaN(gmFmtVer))
            yield GM.setValue(`_uucfgver-${this.id}`, gmFmtVer = this.formatVersion);
          let parsed = yield this.deserializeData(gmData, isEncoded);
          if (gmFmtVer < this.formatVersion && this.migrations)
            parsed = yield this.runMigrations(parsed, gmFmtVer);
          return __spreadValues({}, this.cachedData = parsed);
        } catch (err) {
          console.warn("Error while parsing JSON data, resetting it to the default value.", err);
          yield this.saveDefaultData();
          return this.defaultData;
        }
      });
    }
    /**
     * Returns a copy of the data from the in-memory cache.  
     * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
     */
    getData() {
      return this.deepCopy(this.cachedData);
    }
    /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
    setData(data) {
      this.cachedData = data;
      const useEncoding = Boolean(this.encodeData && this.decodeData);
      return new Promise((resolve) => __async(this, null, function* () {
        yield Promise.all([
          GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(data, useEncoding)),
          GM.setValue(`_uucfgver-${this.id}`, this.formatVersion),
          GM.setValue(`_uucfgenc-${this.id}`, useEncoding)
        ]);
        resolve();
      }));
    }
    /** Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
    saveDefaultData() {
      return __async(this, null, function* () {
        this.cachedData = this.defaultData;
        const useEncoding = Boolean(this.encodeData && this.decodeData);
        return new Promise((resolve) => __async(this, null, function* () {
          yield Promise.all([
            GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultData, useEncoding)),
            GM.setValue(`_uucfgver-${this.id}`, this.formatVersion),
            GM.setValue(`_uucfgenc-${this.id}`, useEncoding)
          ]);
          resolve();
        }));
      });
    }
    /**
     * Call this method to clear all persistently stored data associated with this DataStore instance.  
     * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}  
     * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.  
     *   
     * ⚠️ This requires the additional directive `@grant GM.deleteValue`
     */
    deleteData() {
      return __async(this, null, function* () {
        yield Promise.all([
          GM.deleteValue(`_uucfg-${this.id}`),
          GM.deleteValue(`_uucfgver-${this.id}`),
          GM.deleteValue(`_uucfgenc-${this.id}`)
        ]);
      });
    }
    /** Runs all necessary migration functions consecutively - may be overwritten in a subclass */
    runMigrations(oldData, oldFmtVer) {
      return __async(this, null, function* () {
        if (!this.migrations)
          return oldData;
        let newData = oldData;
        const sortedMigrations = Object.entries(this.migrations).sort(([a], [b]) => Number(a) - Number(b));
        let lastFmtVer = oldFmtVer;
        for (const [fmtVer, migrationFunc] of sortedMigrations) {
          const ver = Number(fmtVer);
          if (oldFmtVer < this.formatVersion && oldFmtVer < ver) {
            try {
              const migRes = migrationFunc(newData);
              newData = migRes instanceof Promise ? yield migRes : migRes;
              lastFmtVer = oldFmtVer = ver;
            } catch (err) {
              console.error(`Error while running migration function for format version '${fmtVer}' - resetting to the default value.`, err);
              yield this.saveDefaultData();
              return this.getData();
            }
          }
        }
        yield Promise.all([
          GM.setValue(`_uucfg-${this.id}`, yield this.serializeData(newData)),
          GM.setValue(`_uucfgver-${this.id}`, lastFmtVer),
          GM.setValue(`_uucfgenc-${this.id}`, Boolean(this.encodeData && this.decodeData))
        ]);
        return newData;
      });
    }
    /** Serializes the data using the optional this.encodeData() and returns it as a string */
    serializeData(data, useEncoding = true) {
      return __async(this, null, function* () {
        const stringData = JSON.stringify(data);
        if (!this.encodeData || !this.decodeData || !useEncoding)
          return stringData;
        const encRes = this.encodeData(stringData);
        if (encRes instanceof Promise)
          return yield encRes;
        return encRes;
      });
    }
    /** Deserializes the data using the optional this.decodeData() and returns it as a JSON object */
    deserializeData(data, useEncoding = true) {
      return __async(this, null, function* () {
        let decRes = this.decodeData && this.encodeData && useEncoding ? this.decodeData(data) : void 0;
        if (decRes instanceof Promise)
          decRes = yield decRes;
        return JSON.parse(decRes != null ? decRes : data);
      });
    }
    /** Copies a JSON-compatible object and loses its internal references */
    deepCopy(obj) {
      return JSON.parse(JSON.stringify(obj));
    }
  };

  // lib/dom.ts
  function getUnsafeWindow() {
    try {
      return unsafeWindow;
    } catch (e) {
      return window;
    }
  }
  function insertAfter(beforeElement, afterElement) {
    var _a;
    (_a = beforeElement.parentNode) == null ? void 0 : _a.insertBefore(afterElement, beforeElement.nextSibling);
    return afterElement;
  }
  function addParent(element, newParent) {
    const oldParent = element.parentNode;
    if (!oldParent)
      throw new Error("Element doesn't have a parent node");
    oldParent.replaceChild(newParent, element);
    newParent.appendChild(element);
    return newParent;
  }
  function addGlobalStyle(style) {
    const styleElem = document.createElement("style");
    styleElem.innerHTML = style;
    document.head.appendChild(styleElem);
    return styleElem;
  }
  function preloadImages(srcUrls, rejects = false) {
    const promises = srcUrls.map((src) => new Promise((res, rej) => {
      const image = new Image();
      image.src = src;
      image.addEventListener("load", () => res(image));
      image.addEventListener("error", (evt) => rejects && rej(evt));
    }));
    return Promise.allSettled(promises);
  }
  function openInNewTab(href, background) {
    try {
      GM.openInTab(href, background);
    } catch (e) {
      const openElem = document.createElement("a");
      Object.assign(openElem, {
        className: "userutils-open-in-new-tab",
        target: "_blank",
        rel: "noopener noreferrer",
        href
      });
      openElem.style.display = "none";
      document.body.appendChild(openElem);
      openElem.click();
      setTimeout(openElem.remove, 50);
    }
  }
  function interceptEvent(eventObject, eventName, predicate = () => true) {
    Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
    if (isNaN(Error.stackTraceLimit))
      Error.stackTraceLimit = 100;
    (function(original) {
      eventObject.__proto__.addEventListener = function(...args) {
        var _a, _b;
        const origListener = typeof args[1] === "function" ? args[1] : (_b = (_a = args[1]) == null ? void 0 : _a.handleEvent) != null ? _b : () => void 0;
        args[1] = function(...a) {
          if (args[0] === eventName && predicate(Array.isArray(a) ? a[0] : a))
            return;
          else
            return origListener.apply(this, a);
        };
        original.apply(this, args);
      };
    })(eventObject.__proto__.addEventListener);
  }
  function interceptWindowEvent(eventName, predicate = () => true) {
    return interceptEvent(getUnsafeWindow(), eventName, predicate);
  }
  function isScrollable(element) {
    const { overflowX, overflowY } = getComputedStyle(element);
    return {
      vertical: (overflowY === "scroll" || overflowY === "auto") && element.scrollHeight > element.clientHeight,
      horizontal: (overflowX === "scroll" || overflowX === "auto") && element.scrollWidth > element.clientWidth
    };
  }
  function observeElementProp(element, property, callback) {
    const elementPrototype = Object.getPrototypeOf(element);
    if (elementPrototype.hasOwnProperty(property)) {
      const descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
      Object.defineProperty(element, property, {
        get: function() {
          var _a;
          return (_a = descriptor == null ? void 0 : descriptor.get) == null ? void 0 : _a.apply(this, arguments);
        },
        set: function() {
          var _a;
          const oldValue = this[property];
          (_a = descriptor == null ? void 0 : descriptor.set) == null ? void 0 : _a.apply(this, arguments);
          const newValue = this[property];
          if (typeof callback === "function") {
            callback.bind(this, oldValue, newValue);
          }
          return newValue;
        }
      });
    }
  }
  function getSiblingsFrame(refElement, siblingAmount, refElementAlignment = "center-top", includeRef = true) {
    var _a, _b;
    const siblings = [...(_b = (_a = refElement.parentNode) == null ? void 0 : _a.childNodes) != null ? _b : []];
    const elemSiblIdx = siblings.indexOf(refElement);
    if (elemSiblIdx === -1)
      throw new Error("Element doesn't have a parent node");
    if (refElementAlignment === "top")
      return [...siblings.slice(elemSiblIdx + Number(!includeRef), elemSiblIdx + siblingAmount + Number(!includeRef))];
    else if (refElementAlignment.startsWith("center-")) {
      const halfAmount = (refElementAlignment === "center-bottom" ? Math.ceil : Math.floor)(siblingAmount / 2);
      const startIdx = Math.max(0, elemSiblIdx - halfAmount);
      const topOffset = Number(refElementAlignment === "center-top" && siblingAmount % 2 === 0 && includeRef);
      const btmOffset = Number(refElementAlignment === "center-bottom" && siblingAmount % 2 !== 0 && includeRef);
      const startIdxWithOffset = startIdx + topOffset + btmOffset;
      return [
        ...siblings.filter((_, idx) => includeRef || idx !== elemSiblIdx).slice(startIdxWithOffset, startIdxWithOffset + siblingAmount)
      ];
    } else if (refElementAlignment === "bottom")
      return [...siblings.slice(elemSiblIdx - siblingAmount + Number(includeRef), elemSiblIdx + Number(includeRef))];
    return [];
  }

  // lib/misc.ts
  function autoPlural(word, num) {
    if (Array.isArray(num) || num instanceof NodeList)
      num = num.length;
    return `${word}${num === 1 ? "" : "s"}`;
  }
  function pauseFor(time) {
    return new Promise((res) => {
      setTimeout(() => res(), time);
    });
  }
  function debounce(func, timeout = 300, edge = "falling") {
    let timer;
    return function(...args) {
      if (edge === "rising") {
        if (!timer) {
          func.apply(this, args);
          timer = setTimeout(() => timer = void 0, timeout);
        }
      } else {
        clearTimeout(timer);
        timer = setTimeout(() => func.apply(this, args), timeout);
      }
    };
  }
  function fetchAdvanced(_0) {
    return __async(this, arguments, function* (input, options = {}) {
      const { timeout = 1e4 } = options;
      let signalOpts = {}, id = void 0;
      if (timeout >= 0) {
        const controller = new AbortController();
        id = setTimeout(() => controller.abort(), timeout);
        signalOpts = { signal: controller.signal };
      }
      const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
      clearTimeout(id);
      return res;
    });
  }
  function insertValues(input, ...values) {
    return input.replace(/%\d/gm, (match) => {
      var _a, _b;
      const argIndex = Number(match.substring(1)) - 1;
      return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? void 0 : _b.toString();
    });
  }
  function compress(input, compressionFormat, outputType = "string") {
    return __async(this, null, function* () {
      const byteArray = typeof input === "string" ? new TextEncoder().encode(input) : input;
      const comp = new CompressionStream(compressionFormat);
      const writer = comp.writable.getWriter();
      writer.write(byteArray);
      writer.close();
      const buf = yield new Response(comp.readable).arrayBuffer();
      return outputType === "arrayBuffer" ? buf : ab2str(buf);
    });
  }
  function decompress(input, compressionFormat, outputType = "string") {
    return __async(this, null, function* () {
      const byteArray = typeof input === "string" ? str2ab(input) : input;
      const decomp = new DecompressionStream(compressionFormat);
      const writer = decomp.writable.getWriter();
      writer.write(byteArray);
      writer.close();
      const buf = yield new Response(decomp.readable).arrayBuffer();
      return outputType === "arrayBuffer" ? buf : new TextDecoder().decode(buf);
    });
  }
  function ab2str(buf) {
    return getUnsafeWindow().btoa(
      new Uint8Array(buf).reduce((data, byte) => data + String.fromCharCode(byte), "")
    );
  }
  function str2ab(str) {
    return Uint8Array.from(getUnsafeWindow().atob(str), (c) => c.charCodeAt(0));
  }

  // lib/SelectorObserver.ts
  var SelectorObserver = class {
    constructor(baseElement, options = {}) {
      __publicField(this, "enabled", false);
      __publicField(this, "baseElement");
      __publicField(this, "observer");
      __publicField(this, "observerOptions");
      __publicField(this, "customOptions");
      __publicField(this, "listenerMap");
      this.baseElement = baseElement;
      this.listenerMap = /* @__PURE__ */ new Map();
      this.observer = new MutationObserver(() => this.checkAllSelectors());
      const _a = options, {
        defaultDebounce,
        defaultDebounceEdge,
        disableOnNoListeners,
        enableOnAddListener
      } = _a, observerOptions = __objRest(_a, [
        "defaultDebounce",
        "defaultDebounceEdge",
        "disableOnNoListeners",
        "enableOnAddListener"
      ]);
      this.observerOptions = __spreadValues({
        childList: true,
        subtree: true
      }, observerOptions);
      this.customOptions = {
        defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
        defaultDebounceEdge: defaultDebounceEdge != null ? defaultDebounceEdge : "rising",
        disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
        enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
      };
    }
    checkAllSelectors() {
      for (const [selector, listeners] of this.listenerMap.entries())
        this.checkSelector(selector, listeners);
    }
    checkSelector(selector, listeners) {
      var _a;
      if (!this.enabled)
        return;
      const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement;
      if (!baseElement)
        return;
      const all = listeners.some((listener) => listener.all);
      const one = listeners.some((listener) => !listener.all);
      const allElements = all ? baseElement.querySelectorAll(selector) : null;
      const oneElement = one ? baseElement.querySelector(selector) : null;
      for (const options of listeners) {
        if (options.all) {
          if (allElements && allElements.length > 0) {
            options.listener(allElements);
            if (!options.continuous)
              this.removeListener(selector, options);
          }
        } else {
          if (oneElement) {
            options.listener(oneElement);
            if (!options.continuous)
              this.removeListener(selector, options);
          }
        }
        if (((_a = this.listenerMap.get(selector)) == null ? void 0 : _a.length) === 0)
          this.listenerMap.delete(selector);
        if (this.listenerMap.size === 0 && this.customOptions.disableOnNoListeners)
          this.disable();
      }
    }
    debounce(func, time, edge = "falling") {
      return debounce(func, time, edge);
    }
    /**
     * Starts observing the children of the base element for changes to the given {@linkcode selector} according to the set {@linkcode options}
     * @param selector The selector to observe
     * @param options Options for the selector observation
     * @param options.listener Gets called whenever the selector was found in the DOM
     * @param [options.all] Whether to use `querySelectorAll()` instead - default is false
     * @param [options.continuous] Whether to call the listener continuously instead of just once - default is false
     * @param [options.debounce] Whether to debounce the listener to reduce calls to `querySelector` or `querySelectorAll` - set undefined or <=0 to disable (default)
     */
    addListener(selector, options) {
      options = __spreadValues({ all: false, continuous: false, debounce: 0 }, options);
      if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
        options.listener = this.debounce(
          options.listener,
          options.debounce || this.customOptions.defaultDebounce,
          options.debounceEdge || this.customOptions.defaultDebounceEdge
        );
      }
      if (this.listenerMap.has(selector))
        this.listenerMap.get(selector).push(options);
      else
        this.listenerMap.set(selector, [options]);
      if (this.enabled === false && this.customOptions.enableOnAddListener)
        this.enable();
      this.checkSelector(selector, [options]);
    }
    /** Disables the observation of the child elements */
    disable() {
      if (!this.enabled)
        return;
      this.enabled = false;
      this.observer.disconnect();
    }
    /**
     * Enables or reenables the observation of the child elements.
     * @param immediatelyCheckSelectors Whether to immediately check if all previously registered selectors exist (default is true)
     * @returns Returns true when the observation was enabled, false otherwise (e.g. when the base element wasn't found)
     */
    enable(immediatelyCheckSelectors = true) {
      const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement;
      if (this.enabled || !baseElement)
        return false;
      this.enabled = true;
      this.observer.observe(baseElement, this.observerOptions);
      if (immediatelyCheckSelectors)
        this.checkAllSelectors();
      return true;
    }
    /** Returns whether the observation of the child elements is currently enabled */
    isEnabled() {
      return this.enabled;
    }
    /** Removes all listeners that have been registered with {@linkcode addListener()} */
    clearListeners() {
      this.listenerMap.clear();
    }
    /**
     * Removes all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()}
     * @returns Returns true when all listeners for the associated selector were found and removed, false otherwise
     */
    removeAllListeners(selector) {
      return this.listenerMap.delete(selector);
    }
    /**
     * Removes a single listener for the given {@linkcode selector} and {@linkcode options} that has been registered with {@linkcode addListener()}
     * @returns Returns true when the listener was found and removed, false otherwise
     */
    removeListener(selector, options) {
      const listeners = this.listenerMap.get(selector);
      if (!listeners)
        return false;
      const index = listeners.indexOf(options);
      if (index > -1) {
        listeners.splice(index, 1);
        return true;
      }
      return false;
    }
    /** Returns all listeners that have been registered with {@linkcode addListener()} */
    getAllListeners() {
      return this.listenerMap;
    }
    /** Returns all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()} */
    getListeners(selector) {
      return this.listenerMap.get(selector);
    }
  };

  // lib/translation.ts
  var trans = {};
  var curLang;
  function tr(key, ...args) {
    var _a;
    if (!curLang)
      return key;
    const trText = (_a = trans[curLang]) == null ? void 0 : _a[key];
    if (!trText)
      return key;
    if (args.length > 0 && trText.match(/%\d/)) {
      return insertValues(trText, ...args);
    }
    return trText;
  }
  tr.addLanguage = (language, translations) => {
    trans[language] = translations;
  };
  tr.setLanguage = (language) => {
    curLang = language;
  };
  tr.getLanguage = () => {
    return curLang;
  };

  exports.DataStore = DataStore;
  exports.SelectorObserver = SelectorObserver;
  exports.addGlobalStyle = addGlobalStyle;
  exports.addParent = addParent;
  exports.autoPlural = autoPlural;
  exports.clamp = clamp;
  exports.compress = compress;
  exports.debounce = debounce;
  exports.decompress = decompress;
  exports.fetchAdvanced = fetchAdvanced;
  exports.getSiblingsFrame = getSiblingsFrame;
  exports.getUnsafeWindow = getUnsafeWindow;
  exports.insertAfter = insertAfter;
  exports.insertValues = insertValues;
  exports.interceptEvent = interceptEvent;
  exports.interceptWindowEvent = interceptWindowEvent;
  exports.isScrollable = isScrollable;
  exports.mapRange = mapRange;
  exports.observeElementProp = observeElementProp;
  exports.openInNewTab = openInNewTab;
  exports.pauseFor = pauseFor;
  exports.preloadImages = preloadImages;
  exports.randRange = randRange;
  exports.randomId = randomId;
  exports.randomItem = randomItem;
  exports.randomItemIndex = randomItemIndex;
  exports.randomizeArray = randomizeArray;
  exports.takeRandomItem = takeRandomItem;
  exports.tr = tr;

  return exports;

})({});