UserUtils

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

目前為 2024-08-08 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/472956/1424529/UserUtils.js

  1. // ==UserScript==
  2. // @namespace https://github.com/Sv443-Network/UserUtils
  3. // @exclude *
  4. // @author Sv443
  5. // @supportURL https://github.com/Sv443-Network/UserUtils/issues
  6. // @homepageURL https://github.com/Sv443-Network/UserUtils
  7.  
  8. // ==UserLibrary==
  9. // @name UserUtils
  10. // @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
  11. // @version 7.1.0
  12. // @license MIT
  13. // @copyright Sv443 (https://github.com/Sv443)
  14.  
  15. // ==/UserScript==
  16. // ==/UserLibrary==
  17.  
  18. // ==OpenUserJS==
  19. // @author Sv443
  20. // ==/OpenUserJS==
  21.  
  22. var UserUtils = (function (exports) {
  23. var __defProp = Object.defineProperty;
  24. var __getOwnPropSymbols = Object.getOwnPropertySymbols;
  25. var __hasOwnProp = Object.prototype.hasOwnProperty;
  26. var __propIsEnum = Object.prototype.propertyIsEnumerable;
  27. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  28. var __spreadValues = (a, b) => {
  29. for (var prop in b || (b = {}))
  30. if (__hasOwnProp.call(b, prop))
  31. __defNormalProp(a, prop, b[prop]);
  32. if (__getOwnPropSymbols)
  33. for (var prop of __getOwnPropSymbols(b)) {
  34. if (__propIsEnum.call(b, prop))
  35. __defNormalProp(a, prop, b[prop]);
  36. }
  37. return a;
  38. };
  39. var __objRest = (source, exclude) => {
  40. var target = {};
  41. for (var prop in source)
  42. if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
  43. target[prop] = source[prop];
  44. if (source != null && __getOwnPropSymbols)
  45. for (var prop of __getOwnPropSymbols(source)) {
  46. if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
  47. target[prop] = source[prop];
  48. }
  49. return target;
  50. };
  51. var __publicField = (obj, key, value) => {
  52. __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
  53. return value;
  54. };
  55. var __async = (__this, __arguments, generator) => {
  56. return new Promise((resolve, reject) => {
  57. var fulfilled = (value) => {
  58. try {
  59. step(generator.next(value));
  60. } catch (e) {
  61. reject(e);
  62. }
  63. };
  64. var rejected = (value) => {
  65. try {
  66. step(generator.throw(value));
  67. } catch (e) {
  68. reject(e);
  69. }
  70. };
  71. var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
  72. step((generator = generator.apply(__this, __arguments)).next());
  73. });
  74. };
  75.  
  76. // lib/math.ts
  77. function clamp(value, min, max) {
  78. return Math.max(Math.min(value, max), min);
  79. }
  80. function mapRange(value, range1min, range1max, range2min, range2max) {
  81. if (Number(range1min) === 0 && Number(range2min) === 0)
  82. return value * (range2max / range1max);
  83. return (value - range1min) * ((range2max - range2min) / (range1max - range1min)) + range2min;
  84. }
  85. function randRange(...args) {
  86. let min, max;
  87. if (typeof args[0] === "number" && typeof args[1] === "number")
  88. [min, max] = args;
  89. else if (typeof args[0] === "number" && typeof args[1] !== "number") {
  90. min = 0;
  91. [max] = args;
  92. } else
  93. throw new TypeError(`Wrong parameter(s) provided - expected: "number" and "number|undefined", got: "${typeof args[0]}" and "${typeof args[1]}"`);
  94. min = Number(min);
  95. max = Number(max);
  96. if (isNaN(min) || isNaN(max))
  97. return NaN;
  98. if (min > max)
  99. throw new TypeError(`Parameter "min" can't be bigger than "max"`);
  100. return Math.floor(Math.random() * (max - min + 1)) + min;
  101. }
  102.  
  103. // lib/array.ts
  104. function randomItem(array) {
  105. return randomItemIndex(array)[0];
  106. }
  107. function randomItemIndex(array) {
  108. if (array.length === 0)
  109. return [void 0, void 0];
  110. const idx = randRange(array.length - 1);
  111. return [array[idx], idx];
  112. }
  113. function takeRandomItem(arr) {
  114. const [itm, idx] = randomItemIndex(arr);
  115. if (idx === void 0)
  116. return void 0;
  117. arr.splice(idx, 1);
  118. return itm;
  119. }
  120. function randomizeArray(array) {
  121. const retArray = [...array];
  122. if (array.length === 0)
  123. return retArray;
  124. for (let i = retArray.length - 1; i > 0; i--) {
  125. const j = Math.floor(randRange(0, 1e4) / 1e4 * (i + 1));
  126. [retArray[i], retArray[j]] = [retArray[j], retArray[i]];
  127. }
  128. return retArray;
  129. }
  130.  
  131. // lib/DataStore.ts
  132. var DataStore = class {
  133. /**
  134. * Creates an instance of DataStore to manage a sync & async database that is cached in memory and persistently saved across sessions.
  135. * Supports migrating data from older versions to newer ones and populating the cache with default data if no persistent data is found.
  136. *
  137. * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue` if the storageMethod is left as the default of `"GM"`
  138. * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultData`
  139. *
  140. * @template TData The type of the data that is saved in persistent storage for the currently set format version (will be automatically inferred from `defaultData` if not provided) - **This has to be a JSON-compatible object!** (no undefined, circular references, etc.)
  141. * @param options The options for this DataStore instance
  142. */
  143. constructor(options) {
  144. __publicField(this, "id");
  145. __publicField(this, "formatVersion");
  146. __publicField(this, "defaultData");
  147. __publicField(this, "encodeData");
  148. __publicField(this, "decodeData");
  149. __publicField(this, "storageMethod");
  150. __publicField(this, "cachedData");
  151. __publicField(this, "migrations");
  152. var _a;
  153. this.id = options.id;
  154. this.formatVersion = options.formatVersion;
  155. this.defaultData = options.defaultData;
  156. this.cachedData = options.defaultData;
  157. this.migrations = options.migrations;
  158. this.storageMethod = (_a = options.storageMethod) != null ? _a : "GM";
  159. this.encodeData = options.encodeData;
  160. this.decodeData = options.decodeData;
  161. }
  162. //#region public
  163. /**
  164. * Loads the data saved in persistent storage into the in-memory cache and also returns it.
  165. * Automatically populates persistent storage with default data if it doesn't contain any data yet.
  166. * Also runs all necessary migration functions if the data format has changed since the last time the data was saved.
  167. */
  168. loadData() {
  169. return __async(this, null, function* () {
  170. try {
  171. const gmData = yield this.getValue(`_uucfg-${this.id}`, JSON.stringify(this.defaultData));
  172. let gmFmtVer = Number(yield this.getValue(`_uucfgver-${this.id}`, NaN));
  173. if (typeof gmData !== "string") {
  174. yield this.saveDefaultData();
  175. return __spreadValues({}, this.defaultData);
  176. }
  177. const isEncoded = Boolean(yield this.getValue(`_uucfgenc-${this.id}`, false));
  178. let saveData = false;
  179. if (isNaN(gmFmtVer)) {
  180. yield this.setValue(`_uucfgver-${this.id}`, gmFmtVer = this.formatVersion);
  181. saveData = true;
  182. }
  183. let parsed = yield this.deserializeData(gmData, isEncoded);
  184. if (gmFmtVer < this.formatVersion && this.migrations)
  185. parsed = yield this.runMigrations(parsed, gmFmtVer);
  186. if (saveData)
  187. yield this.setData(parsed);
  188. this.cachedData = __spreadValues({}, parsed);
  189. return this.cachedData;
  190. } catch (err) {
  191. console.warn("Error while parsing JSON data, resetting it to the default value.", err);
  192. yield this.saveDefaultData();
  193. return this.defaultData;
  194. }
  195. });
  196. }
  197. /**
  198. * Returns a copy of the data from the in-memory cache.
  199. * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage).
  200. * @param deepCopy Whether to return a deep copy of the data (default: `false`) - only necessary if your data object is nested and may have a bigger performance impact if enabled
  201. */
  202. getData(deepCopy = false) {
  203. return deepCopy ? this.deepCopy(this.cachedData) : __spreadValues({}, this.cachedData);
  204. }
  205. /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */
  206. setData(data) {
  207. this.cachedData = data;
  208. const useEncoding = this.encodingEnabled();
  209. return new Promise((resolve) => __async(this, null, function* () {
  210. yield Promise.all([
  211. this.setValue(`_uucfg-${this.id}`, yield this.serializeData(data, useEncoding)),
  212. this.setValue(`_uucfgver-${this.id}`, this.formatVersion),
  213. this.setValue(`_uucfgenc-${this.id}`, useEncoding)
  214. ]);
  215. resolve();
  216. }));
  217. }
  218. /** Saves the default data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */
  219. saveDefaultData() {
  220. return __async(this, null, function* () {
  221. this.cachedData = this.defaultData;
  222. const useEncoding = this.encodingEnabled();
  223. return new Promise((resolve) => __async(this, null, function* () {
  224. yield Promise.all([
  225. this.setValue(`_uucfg-${this.id}`, yield this.serializeData(this.defaultData, useEncoding)),
  226. this.setValue(`_uucfgver-${this.id}`, this.formatVersion),
  227. this.setValue(`_uucfgenc-${this.id}`, useEncoding)
  228. ]);
  229. resolve();
  230. }));
  231. });
  232. }
  233. /**
  234. * Call this method to clear all persistently stored data associated with this DataStore instance.
  235. * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()}
  236. * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data.
  237. *
  238. * ⚠️ This requires the additional directive `@grant GM.deleteValue`
  239. */
  240. deleteData() {
  241. return __async(this, null, function* () {
  242. yield Promise.all([
  243. this.deleteValue(`_uucfg-${this.id}`),
  244. this.deleteValue(`_uucfgver-${this.id}`),
  245. this.deleteValue(`_uucfgenc-${this.id}`)
  246. ]);
  247. });
  248. }
  249. /** Returns whether encoding and decoding are enabled for this DataStore instance */
  250. encodingEnabled() {
  251. return Boolean(this.encodeData && this.decodeData);
  252. }
  253. //#region migrations
  254. /**
  255. * Runs all necessary migration functions consecutively and saves the result to the in-memory cache and persistent storage and also returns it.
  256. * This method is automatically called by {@linkcode loadData()} if the data format has changed since the last time the data was saved.
  257. * Though calling this method manually is not necessary, it can be useful if you want to run migrations for special occasions like a user importing potentially outdated data that has been previously exported.
  258. *
  259. * If one of the migrations fails, the data will be reset to the default value if `resetOnError` is set to `true` (default). Otherwise, an error will be thrown and no data will be saved.
  260. */
  261. runMigrations(oldData, oldFmtVer, resetOnError = true) {
  262. return __async(this, null, function* () {
  263. if (!this.migrations)
  264. return oldData;
  265. let newData = oldData;
  266. const sortedMigrations = Object.entries(this.migrations).sort(([a], [b]) => Number(a) - Number(b));
  267. let lastFmtVer = oldFmtVer;
  268. for (const [fmtVer, migrationFunc] of sortedMigrations) {
  269. const ver = Number(fmtVer);
  270. if (oldFmtVer < this.formatVersion && oldFmtVer < ver) {
  271. try {
  272. const migRes = migrationFunc(newData);
  273. newData = migRes instanceof Promise ? yield migRes : migRes;
  274. lastFmtVer = oldFmtVer = ver;
  275. } catch (err) {
  276. if (!resetOnError)
  277. throw new Error(`Error while running migration function for format version '${fmtVer}'`);
  278. console.error(`Error while running migration function for format version '${fmtVer}' - resetting to the default value.`, err);
  279. yield this.saveDefaultData();
  280. return this.getData();
  281. }
  282. }
  283. }
  284. yield Promise.all([
  285. this.setValue(`_uucfg-${this.id}`, yield this.serializeData(newData)),
  286. this.setValue(`_uucfgver-${this.id}`, lastFmtVer),
  287. this.setValue(`_uucfgenc-${this.id}`, this.encodingEnabled())
  288. ]);
  289. return this.cachedData = __spreadValues({}, newData);
  290. });
  291. }
  292. //#region serialization
  293. /** Serializes the data using the optional this.encodeData() and returns it as a string */
  294. serializeData(data, useEncoding = true) {
  295. return __async(this, null, function* () {
  296. const stringData = JSON.stringify(data);
  297. if (!this.encodingEnabled() || !useEncoding)
  298. return stringData;
  299. const encRes = this.encodeData(stringData);
  300. if (encRes instanceof Promise)
  301. return yield encRes;
  302. return encRes;
  303. });
  304. }
  305. /** Deserializes the data using the optional this.decodeData() and returns it as a JSON object */
  306. deserializeData(data, useEncoding = true) {
  307. return __async(this, null, function* () {
  308. let decRes = this.encodingEnabled() && useEncoding ? this.decodeData(data) : void 0;
  309. if (decRes instanceof Promise)
  310. decRes = yield decRes;
  311. return JSON.parse(decRes != null ? decRes : data);
  312. });
  313. }
  314. //#region misc
  315. /** Copies a JSON-compatible object and loses all its internal references in the process */
  316. deepCopy(obj) {
  317. return JSON.parse(JSON.stringify(obj));
  318. }
  319. //#region storage
  320. /** Gets a value from persistent storage - can be overwritten in a subclass if you want to use something other than GM storage */
  321. getValue(name, defaultValue) {
  322. return __async(this, null, function* () {
  323. var _a, _b;
  324. switch (this.storageMethod) {
  325. case "localStorage":
  326. return (_a = localStorage.getItem(name)) != null ? _a : defaultValue;
  327. case "sessionStorage":
  328. return (_b = sessionStorage.getItem(name)) != null ? _b : defaultValue;
  329. default:
  330. return GM.getValue(name, defaultValue);
  331. }
  332. });
  333. }
  334. /**
  335. * Sets a value in persistent storage - can be overwritten in a subclass if you want to use something other than GM storage.
  336. * The default storage engines will stringify all passed values like numbers or booleans, so be aware of that.
  337. */
  338. setValue(name, value) {
  339. return __async(this, null, function* () {
  340. switch (this.storageMethod) {
  341. case "localStorage":
  342. return localStorage.setItem(name, String(value));
  343. case "sessionStorage":
  344. return sessionStorage.setItem(name, String(value));
  345. default:
  346. return GM.setValue(name, String(value));
  347. }
  348. });
  349. }
  350. /** Deletes a value from persistent storage - can be overwritten in a subclass if you want to use something other than GM storage */
  351. deleteValue(name) {
  352. return __async(this, null, function* () {
  353. switch (this.storageMethod) {
  354. case "localStorage":
  355. return localStorage.removeItem(name);
  356. case "sessionStorage":
  357. return sessionStorage.removeItem(name);
  358. default:
  359. return GM.deleteValue(name);
  360. }
  361. });
  362. }
  363. };
  364.  
  365. // lib/DataStoreSerializer.ts
  366. var DataStoreSerializer = class {
  367. constructor(stores, options = {}) {
  368. __publicField(this, "stores");
  369. __publicField(this, "options");
  370. if (!getUnsafeWindow().crypto || !getUnsafeWindow().crypto.subtle)
  371. throw new Error("DataStoreSerializer has to run in a secure context (HTTPS)!");
  372. this.stores = stores;
  373. this.options = __spreadValues({
  374. addChecksum: true,
  375. ensureIntegrity: true
  376. }, options);
  377. }
  378. /** Calculates the checksum of a string */
  379. calcChecksum(input) {
  380. return __async(this, null, function* () {
  381. return computeHash(input, "SHA-256");
  382. });
  383. }
  384. /** Serializes a DataStore instance */
  385. serializeStore(storeInst) {
  386. return __async(this, null, function* () {
  387. const data = storeInst.encodingEnabled() ? yield storeInst.encodeData(JSON.stringify(storeInst.getData())) : JSON.stringify(storeInst.getData());
  388. const checksum = this.options.addChecksum ? yield this.calcChecksum(data) : void 0;
  389. return {
  390. id: storeInst.id,
  391. data,
  392. formatVersion: storeInst.formatVersion,
  393. encoded: storeInst.encodingEnabled(),
  394. checksum
  395. };
  396. });
  397. }
  398. /** Serializes the data stores into a string */
  399. serialize() {
  400. return __async(this, null, function* () {
  401. const serData = [];
  402. for (const store of this.stores)
  403. serData.push(yield this.serializeStore(store));
  404. return JSON.stringify(serData);
  405. });
  406. }
  407. /**
  408. * Deserializes the data exported via {@linkcode serialize()} and imports it into the DataStore instances.
  409. * Also triggers the migration process if the data format has changed.
  410. */
  411. deserialize(serializedData) {
  412. return __async(this, null, function* () {
  413. const deserStores = JSON.parse(serializedData);
  414. for (const storeData of deserStores) {
  415. const storeInst = this.stores.find((s) => s.id === storeData.id);
  416. if (!storeInst)
  417. throw new Error(`DataStore instance with ID "${storeData.id}" not found! Make sure to provide it in the DataStoreSerializer constructor.`);
  418. if (this.options.ensureIntegrity && typeof storeData.checksum === "string") {
  419. const checksum = yield this.calcChecksum(storeData.data);
  420. if (checksum !== storeData.checksum)
  421. throw new Error(`Checksum mismatch for DataStore with ID "${storeData.id}"!
  422. Expected: ${storeData.checksum}
  423. Has: ${checksum}`);
  424. }
  425. const decodedData = storeData.encoded && storeInst.encodingEnabled() ? yield storeInst.decodeData(storeData.data) : storeData.data;
  426. if (storeData.formatVersion && !isNaN(Number(storeData.formatVersion)) && Number(storeData.formatVersion) < storeInst.formatVersion)
  427. yield storeInst.runMigrations(JSON.parse(decodedData), Number(storeData.formatVersion), false);
  428. else
  429. yield storeInst.setData(JSON.parse(decodedData));
  430. }
  431. });
  432. }
  433. };
  434.  
  435. // lib/dom.ts
  436. function getUnsafeWindow() {
  437. try {
  438. return unsafeWindow;
  439. } catch (e) {
  440. return window;
  441. }
  442. }
  443. function addParent(element, newParent) {
  444. const oldParent = element.parentNode;
  445. if (!oldParent)
  446. throw new Error("Element doesn't have a parent node");
  447. oldParent.replaceChild(newParent, element);
  448. newParent.appendChild(element);
  449. return newParent;
  450. }
  451. function addGlobalStyle(style) {
  452. const styleElem = document.createElement("style");
  453. styleElem.innerHTML = style;
  454. document.head.appendChild(styleElem);
  455. return styleElem;
  456. }
  457. function preloadImages(srcUrls, rejects = false) {
  458. const promises = srcUrls.map((src) => new Promise((res, rej) => {
  459. const image = new Image();
  460. image.src = src;
  461. image.addEventListener("load", () => res(image));
  462. image.addEventListener("error", (evt) => rejects && rej(evt));
  463. }));
  464. return Promise.allSettled(promises);
  465. }
  466. function openInNewTab(href, background) {
  467. try {
  468. GM.openInTab(href, background);
  469. } catch (e) {
  470. const openElem = document.createElement("a");
  471. Object.assign(openElem, {
  472. className: "userutils-open-in-new-tab",
  473. target: "_blank",
  474. rel: "noopener noreferrer",
  475. href
  476. });
  477. openElem.style.display = "none";
  478. document.body.appendChild(openElem);
  479. openElem.click();
  480. setTimeout(openElem.remove, 50);
  481. }
  482. }
  483. function interceptEvent(eventObject, eventName, predicate = () => true) {
  484. Error.stackTraceLimit = Math.max(Error.stackTraceLimit, 100);
  485. if (isNaN(Error.stackTraceLimit))
  486. Error.stackTraceLimit = 100;
  487. (function(original) {
  488. eventObject.__proto__.addEventListener = function(...args) {
  489. var _a, _b;
  490. const origListener = typeof args[1] === "function" ? args[1] : (_b = (_a = args[1]) == null ? void 0 : _a.handleEvent) != null ? _b : () => void 0;
  491. args[1] = function(...a) {
  492. if (args[0] === eventName && predicate(Array.isArray(a) ? a[0] : a))
  493. return;
  494. else
  495. return origListener.apply(this, a);
  496. };
  497. original.apply(this, args);
  498. };
  499. })(eventObject.__proto__.addEventListener);
  500. }
  501. function interceptWindowEvent(eventName, predicate = () => true) {
  502. return interceptEvent(getUnsafeWindow(), eventName, predicate);
  503. }
  504. function isScrollable(element) {
  505. const { overflowX, overflowY } = getComputedStyle(element);
  506. return {
  507. vertical: (overflowY === "scroll" || overflowY === "auto") && element.scrollHeight > element.clientHeight,
  508. horizontal: (overflowX === "scroll" || overflowX === "auto") && element.scrollWidth > element.clientWidth
  509. };
  510. }
  511. function observeElementProp(element, property, callback) {
  512. const elementPrototype = Object.getPrototypeOf(element);
  513. if (elementPrototype.hasOwnProperty(property)) {
  514. const descriptor = Object.getOwnPropertyDescriptor(elementPrototype, property);
  515. Object.defineProperty(element, property, {
  516. get: function() {
  517. var _a;
  518. return (_a = descriptor == null ? void 0 : descriptor.get) == null ? void 0 : _a.apply(this, arguments);
  519. },
  520. set: function() {
  521. var _a;
  522. const oldValue = this[property];
  523. (_a = descriptor == null ? void 0 : descriptor.set) == null ? void 0 : _a.apply(this, arguments);
  524. const newValue = this[property];
  525. if (typeof callback === "function") {
  526. callback.bind(this, oldValue, newValue);
  527. }
  528. return newValue;
  529. }
  530. });
  531. }
  532. }
  533. function getSiblingsFrame(refElement, siblingAmount, refElementAlignment = "center-top", includeRef = true) {
  534. var _a, _b;
  535. const siblings = [...(_b = (_a = refElement.parentNode) == null ? void 0 : _a.childNodes) != null ? _b : []];
  536. const elemSiblIdx = siblings.indexOf(refElement);
  537. if (elemSiblIdx === -1)
  538. throw new Error("Element doesn't have a parent node");
  539. if (refElementAlignment === "top")
  540. return [...siblings.slice(elemSiblIdx + Number(!includeRef), elemSiblIdx + siblingAmount + Number(!includeRef))];
  541. else if (refElementAlignment.startsWith("center-")) {
  542. const halfAmount = (refElementAlignment === "center-bottom" ? Math.ceil : Math.floor)(siblingAmount / 2);
  543. const startIdx = Math.max(0, elemSiblIdx - halfAmount);
  544. const topOffset = Number(refElementAlignment === "center-top" && siblingAmount % 2 === 0 && includeRef);
  545. const btmOffset = Number(refElementAlignment === "center-bottom" && siblingAmount % 2 !== 0 && includeRef);
  546. const startIdxWithOffset = startIdx + topOffset + btmOffset;
  547. return [
  548. ...siblings.filter((_, idx) => includeRef || idx !== elemSiblIdx).slice(startIdxWithOffset, startIdxWithOffset + siblingAmount)
  549. ];
  550. } else if (refElementAlignment === "bottom")
  551. return [...siblings.slice(elemSiblIdx - siblingAmount + Number(includeRef), elemSiblIdx + Number(includeRef))];
  552. return [];
  553. }
  554.  
  555. // node_modules/nanoevents/index.js
  556. var createNanoEvents = () => ({
  557. emit(event, ...args) {
  558. for (let i = 0, callbacks = this.events[event] || [], length = callbacks.length; i < length; i++) {
  559. callbacks[i](...args);
  560. }
  561. },
  562. events: {},
  563. on(event, cb) {
  564. var _a;
  565. ((_a = this.events)[event] || (_a[event] = [])).push(cb);
  566. return () => {
  567. var _a2;
  568. this.events[event] = (_a2 = this.events[event]) == null ? void 0 : _a2.filter((i) => cb !== i);
  569. };
  570. }
  571. });
  572.  
  573. // lib/NanoEmitter.ts
  574. var NanoEmitter = class {
  575. constructor(options = {}) {
  576. __publicField(this, "events", createNanoEvents());
  577. __publicField(this, "eventUnsubscribes", []);
  578. __publicField(this, "emitterOptions");
  579. this.emitterOptions = __spreadValues({
  580. publicEmit: false
  581. }, options);
  582. }
  583. /** Subscribes to an event - returns a function that unsubscribes the event listener */
  584. on(event, cb) {
  585. let unsub;
  586. const unsubProxy = () => {
  587. if (!unsub)
  588. return;
  589. unsub();
  590. this.eventUnsubscribes = this.eventUnsubscribes.filter((u) => u !== unsub);
  591. };
  592. unsub = this.events.on(event, cb);
  593. this.eventUnsubscribes.push(unsub);
  594. return unsubProxy;
  595. }
  596. /** Subscribes to an event and calls the callback or resolves the Promise only once */
  597. once(event, cb) {
  598. return new Promise((resolve) => {
  599. let unsub;
  600. const onceProxy = (...args) => {
  601. unsub();
  602. cb == null ? void 0 : cb(...args);
  603. resolve(args);
  604. };
  605. unsub = this.on(event, onceProxy);
  606. });
  607. }
  608. /** Emits an event on this instance - Needs `publicEmit` to be set to true in the constructor! */
  609. emit(event, ...args) {
  610. if (this.emitterOptions.publicEmit) {
  611. this.events.emit(event, ...args);
  612. return true;
  613. }
  614. return false;
  615. }
  616. /** Unsubscribes all event listeners */
  617. unsubscribeAll() {
  618. for (const unsub of this.eventUnsubscribes)
  619. unsub();
  620. this.eventUnsubscribes = [];
  621. }
  622. };
  623.  
  624. // lib/Dialog.ts
  625. var defaultDialogCss = `.uu-no-select {
  626. user-select: none;
  627. }
  628.  
  629. .uu-dialog-bg {
  630. --uu-dialog-bg: #333333;
  631. --uu-dialog-bg-highlight: #252525;
  632. --uu-scroll-indicator-bg: rgba(10, 10, 10, 0.7);
  633. --uu-dialog-separator-color: #797979;
  634. --uu-dialog-border-radius: 10px;
  635. }
  636.  
  637. .uu-dialog-bg {
  638. display: block;
  639. position: fixed;
  640. width: 100%;
  641. height: 100%;
  642. top: 0;
  643. left: 0;
  644. z-index: 5;
  645. background-color: rgba(0, 0, 0, 0.6);
  646. }
  647.  
  648. .uu-dialog {
  649. --uu-calc-dialog-height: calc(min(100vh - 40px, var(--uu-dialog-height-max)));
  650. position: absolute;
  651. display: flex;
  652. flex-direction: column;
  653. width: calc(min(100% - 60px, var(--uu-dialog-width-max)));
  654. border-radius: var(--uu-dialog-border-radius);
  655. height: auto;
  656. max-height: var(--uu-calc-dialog-height);
  657. left: 50%;
  658. top: 50%;
  659. transform: translate(-50%, -50%);
  660. z-index: 6;
  661. color: #fff;
  662. background-color: var(--uu-dialog-bg);
  663. }
  664.  
  665. .uu-dialog.align-top {
  666. top: 0;
  667. transform: translate(-50%, 40px);
  668. }
  669.  
  670. .uu-dialog.align-bottom {
  671. top: 100%;
  672. transform: translate(-50%, -100%);
  673. }
  674.  
  675. .uu-dialog-body {
  676. font-size: 1.5rem;
  677. padding: 20px;
  678. }
  679.  
  680. .uu-dialog-body.small {
  681. padding: 15px;
  682. }
  683.  
  684. #uu-dialog-opts {
  685. display: flex;
  686. flex-direction: column;
  687. position: relative;
  688. padding: 30px 0px;
  689. overflow-y: auto;
  690. }
  691.  
  692. .uu-dialog-header {
  693. display: flex;
  694. justify-content: space-between;
  695. align-items: center;
  696. margin-bottom: 6px;
  697. padding: 15px 20px 15px 20px;
  698. background-color: var(--uu-dialog-bg);
  699. border: 2px solid var(--uu-dialog-separator-color);
  700. border-style: none none solid none !important;
  701. border-radius: var(--uu-dialog-border-radius) var(--uu-dialog-border-radius) 0px 0px;
  702. }
  703.  
  704. .uu-dialog-header.small {
  705. padding: 10px 15px;
  706. border-style: none none solid none !important;
  707. }
  708.  
  709. .uu-dialog-header-pad {
  710. content: " ";
  711. min-height: 32px;
  712. }
  713.  
  714. .uu-dialog-header-pad.small {
  715. min-height: 24px;
  716. }
  717.  
  718. .uu-dialog-titlecont {
  719. display: flex;
  720. align-items: center;
  721. }
  722.  
  723. .uu-dialog-titlecont-no-title {
  724. display: flex;
  725. justify-content: flex-end;
  726. align-items: center;
  727. }
  728.  
  729. .uu-dialog-title {
  730. position: relative;
  731. display: inline-block;
  732. font-size: 22px;
  733. }
  734.  
  735. .uu-dialog-close {
  736. cursor: pointer;
  737. }
  738.  
  739. .uu-dialog-header-img,
  740. .uu-dialog-close
  741. {
  742. width: 32px;
  743. height: 32px;
  744. }
  745.  
  746. .uu-dialog-header-img.small,
  747. .uu-dialog-close.small
  748. {
  749. width: 24px;
  750. height: 24px;
  751. }
  752.  
  753. .uu-dialog-footer {
  754. font-size: 17px;
  755. text-decoration: underline;
  756. }
  757.  
  758. .uu-dialog-footer.hidden {
  759. display: none;
  760. }
  761.  
  762. .uu-dialog-footer-cont {
  763. margin-top: 6px;
  764. padding: 15px 20px;
  765. background: var(--uu-dialog-bg);
  766. background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, var(--uu-dialog-bg) 30%, var(--uu-dialog-bg) 100%);
  767. border: 2px solid var(--uu-dialog-separator-color);
  768. border-style: solid none none none !important;
  769. border-radius: 0px 0px var(--uu-dialog-border-radius) var(--uu-dialog-border-radius);
  770. }
  771.  
  772. .uu-dialog-footer-buttons-cont button:not(:last-of-type) {
  773. margin-right: 15px;
  774. }`;
  775. exports.currentDialogId = null;
  776. var openDialogs = [];
  777. var defaultStrings = {
  778. closeDialogTooltip: "Click to close the dialog"
  779. };
  780. var Dialog = class _Dialog extends NanoEmitter {
  781. constructor(options) {
  782. super();
  783. /** Options passed to the dialog in the constructor */
  784. __publicField(this, "options");
  785. /** ID that gets added to child element IDs - has to be unique and conform to HTML ID naming rules! */
  786. __publicField(this, "id");
  787. /** Strings used in the dialog (used for translations) */
  788. __publicField(this, "strings");
  789. __publicField(this, "dialogOpen", false);
  790. __publicField(this, "dialogMounted", false);
  791. const _a = options, { strings } = _a, opts = __objRest(_a, ["strings"]);
  792. this.strings = __spreadValues(__spreadValues({}, defaultStrings), strings != null ? strings : {});
  793. this.options = __spreadValues({
  794. closeOnBgClick: true,
  795. closeOnEscPress: true,
  796. destroyOnClose: false,
  797. unmountOnClose: true,
  798. removeListenersOnDestroy: true,
  799. smallHeader: false,
  800. verticalAlign: "center",
  801. dialogCss: defaultDialogCss
  802. }, opts);
  803. this.id = opts.id;
  804. }
  805. //#region public
  806. /** Call after DOMContentLoaded to pre-render the dialog and invisibly mount it in the DOM */
  807. mount() {
  808. return __async(this, null, function* () {
  809. if (this.dialogMounted)
  810. return;
  811. this.dialogMounted = true;
  812. if (!document.querySelector("style.uu-dialog-css"))
  813. addGlobalStyle(this.options.dialogCss).classList.add("uu-dialog-css");
  814. const bgElem = document.createElement("div");
  815. bgElem.id = `uu-${this.id}-dialog-bg`;
  816. bgElem.classList.add("uu-dialog-bg");
  817. if (this.options.closeOnBgClick)
  818. bgElem.ariaLabel = bgElem.title = this.getString("closeDialogTooltip");
  819. bgElem.style.setProperty("--uu-dialog-width-max", `${this.options.width}px`);
  820. bgElem.style.setProperty("--uu-dialog-height-max", `${this.options.height}px`);
  821. bgElem.style.visibility = "hidden";
  822. bgElem.style.display = "none";
  823. bgElem.inert = true;
  824. bgElem.appendChild(yield this.getDialogContent());
  825. document.body.appendChild(bgElem);
  826. this.attachListeners(bgElem);
  827. this.events.emit("render");
  828. return bgElem;
  829. });
  830. }
  831. /** Closes the dialog and clears all its contents (unmounts elements from the DOM) in preparation for a new rendering call */
  832. unmount() {
  833. var _a;
  834. this.close();
  835. this.dialogMounted = false;
  836. const clearSelectors = [
  837. `#uu-${this.id}-dialog-bg`,
  838. `#uu-style-dialog-${this.id}`
  839. ];
  840. for (const sel of clearSelectors)
  841. (_a = document.querySelector(sel)) == null ? void 0 : _a.remove();
  842. this.events.emit("clear");
  843. }
  844. /** Clears the DOM of the dialog and then renders it again */
  845. remount() {
  846. return __async(this, null, function* () {
  847. this.unmount();
  848. yield this.mount();
  849. });
  850. }
  851. /**
  852. * Opens the dialog - also mounts it if it hasn't been mounted yet
  853. * Prevents default action and immediate propagation of the passed event
  854. */
  855. open(e) {
  856. return __async(this, null, function* () {
  857. var _a;
  858. e == null ? void 0 : e.preventDefault();
  859. e == null ? void 0 : e.stopImmediatePropagation();
  860. if (this.isOpen())
  861. return;
  862. this.dialogOpen = true;
  863. if (openDialogs.includes(this.id))
  864. throw new Error(`A dialog with the same ID of '${this.id}' already exists and is open!`);
  865. if (!this.isMounted())
  866. yield this.mount();
  867. const dialogBg = document.querySelector(`#uu-${this.id}-dialog-bg`);
  868. if (!dialogBg)
  869. return console.warn(`Couldn't find background element for dialog with ID '${this.id}'`);
  870. dialogBg.style.visibility = "visible";
  871. dialogBg.style.display = "block";
  872. dialogBg.inert = false;
  873. exports.currentDialogId = this.id;
  874. openDialogs.unshift(this.id);
  875. for (const dialogId of openDialogs)
  876. if (dialogId !== this.id)
  877. (_a = document.querySelector(`#uu-${dialogId}-dialog-bg`)) == null ? void 0 : _a.setAttribute("inert", "true");
  878. document.body.classList.remove("uu-no-select");
  879. document.body.setAttribute("inert", "true");
  880. this.events.emit("open");
  881. return dialogBg;
  882. });
  883. }
  884. /** Closes the dialog - prevents default action and immediate propagation of the passed event */
  885. close(e) {
  886. var _a, _b;
  887. e == null ? void 0 : e.preventDefault();
  888. e == null ? void 0 : e.stopImmediatePropagation();
  889. if (!this.isOpen())
  890. return;
  891. this.dialogOpen = false;
  892. const dialogBg = document.querySelector(`#uu-${this.id}-dialog-bg`);
  893. if (!dialogBg)
  894. return console.warn(`Couldn't find background element for dialog with ID '${this.id}'`);
  895. dialogBg.style.visibility = "hidden";
  896. dialogBg.style.display = "none";
  897. dialogBg.inert = true;
  898. openDialogs.splice(openDialogs.indexOf(this.id), 1);
  899. exports.currentDialogId = (_a = openDialogs[0]) != null ? _a : null;
  900. if (exports.currentDialogId)
  901. (_b = document.querySelector(`#uu-${exports.currentDialogId}-dialog-bg`)) == null ? void 0 : _b.removeAttribute("inert");
  902. if (openDialogs.length === 0) {
  903. document.body.classList.add("uu-no-select");
  904. document.body.removeAttribute("inert");
  905. }
  906. this.events.emit("close");
  907. if (this.options.destroyOnClose)
  908. this.destroy();
  909. else if (this.options.unmountOnClose)
  910. this.unmount();
  911. }
  912. /** Returns true if the dialog is currently open */
  913. isOpen() {
  914. return this.dialogOpen;
  915. }
  916. /** Returns true if the dialog is currently mounted */
  917. isMounted() {
  918. return this.dialogMounted;
  919. }
  920. /** Clears the DOM of the dialog and removes all event listeners */
  921. destroy() {
  922. this.unmount();
  923. this.events.emit("destroy");
  924. this.options.removeListenersOnDestroy && this.unsubscribeAll();
  925. }
  926. //#region static
  927. /** Returns the ID of the top-most dialog (the dialog that has been opened last) */
  928. static getCurrentDialogId() {
  929. return exports.currentDialogId;
  930. }
  931. /** Returns the IDs of all currently open dialogs, top-most first */
  932. static getOpenDialogs() {
  933. return openDialogs;
  934. }
  935. //#region protected
  936. getString(key) {
  937. var _a;
  938. return (_a = this.strings[key]) != null ? _a : defaultStrings[key];
  939. }
  940. /** Called once to attach all generic event listeners */
  941. attachListeners(bgElem) {
  942. if (this.options.closeOnBgClick) {
  943. bgElem.addEventListener("click", (e) => {
  944. var _a;
  945. if (this.isOpen() && ((_a = e.target) == null ? void 0 : _a.id) === `uu-${this.id}-dialog-bg`)
  946. this.close(e);
  947. });
  948. }
  949. if (this.options.closeOnEscPress) {
  950. document.body.addEventListener("keydown", (e) => {
  951. if (e.key === "Escape" && this.isOpen() && _Dialog.getCurrentDialogId() === this.id)
  952. this.close(e);
  953. });
  954. }
  955. }
  956. //#region protected
  957. /**
  958. * Adds generic, accessible interaction listeners to the passed element.
  959. * All listeners have the default behavior prevented and stop propagation (for keyboard events only as long as the captured key is valid).
  960. * @param listenerOptions Provide a {@linkcode listenerOptions} object to configure the listeners
  961. */
  962. onInteraction(elem, listener, listenerOptions) {
  963. const _a = listenerOptions != null ? listenerOptions : {}, { preventDefault = true, stopPropagation = true } = _a, listenerOpts = __objRest(_a, ["preventDefault", "stopPropagation"]);
  964. const interactionKeys = ["Enter", " ", "Space"];
  965. const proxListener = (e) => {
  966. if (e instanceof KeyboardEvent) {
  967. if (interactionKeys.includes(e.key)) {
  968. preventDefault && e.preventDefault();
  969. stopPropagation && e.stopPropagation();
  970. } else
  971. return;
  972. } else if (e instanceof MouseEvent) {
  973. preventDefault && e.preventDefault();
  974. stopPropagation && e.stopPropagation();
  975. }
  976. (listenerOpts == null ? void 0 : listenerOpts.once) && e.type === "keydown" && elem.removeEventListener("click", proxListener, listenerOpts);
  977. (listenerOpts == null ? void 0 : listenerOpts.once) && e.type === "click" && elem.removeEventListener("keydown", proxListener, listenerOpts);
  978. listener(e);
  979. };
  980. elem.addEventListener("click", proxListener, listenerOpts);
  981. elem.addEventListener("keydown", proxListener, listenerOpts);
  982. }
  983. /** Returns the dialog content element and all its children */
  984. getDialogContent() {
  985. return __async(this, null, function* () {
  986. var _a, _b, _c, _d;
  987. const header = (_b = (_a = this.options).renderHeader) == null ? void 0 : _b.call(_a);
  988. const footer = (_d = (_c = this.options).renderFooter) == null ? void 0 : _d.call(_c);
  989. const dialogWrapperEl = document.createElement("div");
  990. dialogWrapperEl.id = `uu-${this.id}-dialog`;
  991. dialogWrapperEl.classList.add("uu-dialog");
  992. dialogWrapperEl.ariaLabel = dialogWrapperEl.title = "";
  993. dialogWrapperEl.role = "dialog";
  994. dialogWrapperEl.setAttribute("aria-labelledby", `uu-${this.id}-dialog-title`);
  995. dialogWrapperEl.setAttribute("aria-describedby", `uu-${this.id}-dialog-body`);
  996. if (this.options.verticalAlign !== "center")
  997. dialogWrapperEl.classList.add(`align-${this.options.verticalAlign}`);
  998. const headerWrapperEl = document.createElement("div");
  999. headerWrapperEl.classList.add("uu-dialog-header");
  1000. this.options.small && headerWrapperEl.classList.add("small");
  1001. if (header) {
  1002. const headerTitleWrapperEl = document.createElement("div");
  1003. headerTitleWrapperEl.id = `uu-${this.id}-dialog-title`;
  1004. headerTitleWrapperEl.classList.add("uu-dialog-title-wrapper");
  1005. headerTitleWrapperEl.role = "heading";
  1006. headerTitleWrapperEl.ariaLevel = "1";
  1007. headerTitleWrapperEl.appendChild(header instanceof Promise ? yield header : header);
  1008. headerWrapperEl.appendChild(headerTitleWrapperEl);
  1009. } else {
  1010. const padEl = document.createElement("div");
  1011. padEl.classList.add("uu-dialog-header-pad", this.options.small ? "small" : "");
  1012. headerWrapperEl.appendChild(padEl);
  1013. }
  1014. if (this.options.renderCloseBtn) {
  1015. const closeBtnEl = yield this.options.renderCloseBtn();
  1016. closeBtnEl.classList.add("uu-dialog-close");
  1017. this.options.small && closeBtnEl.classList.add("small");
  1018. closeBtnEl.tabIndex = 0;
  1019. if (closeBtnEl.hasAttribute("alt"))
  1020. closeBtnEl.setAttribute("alt", this.getString("closeDialogTooltip"));
  1021. closeBtnEl.title = closeBtnEl.ariaLabel = this.getString("closeDialogTooltip");
  1022. this.onInteraction(closeBtnEl, () => this.close());
  1023. headerWrapperEl.appendChild(closeBtnEl);
  1024. }
  1025. dialogWrapperEl.appendChild(headerWrapperEl);
  1026. const dialogBodyElem = document.createElement("div");
  1027. dialogBodyElem.id = `uu-${this.id}-dialog-body`;
  1028. dialogBodyElem.classList.add("uu-dialog-body");
  1029. this.options.small && dialogBodyElem.classList.add("small");
  1030. const body = this.options.renderBody();
  1031. dialogBodyElem.appendChild(body instanceof Promise ? yield body : body);
  1032. dialogWrapperEl.appendChild(dialogBodyElem);
  1033. if (footer) {
  1034. const footerWrapper = document.createElement("div");
  1035. footerWrapper.classList.add("uu-dialog-footer-cont");
  1036. dialogWrapperEl.appendChild(footerWrapper);
  1037. footerWrapper.appendChild(footer instanceof Promise ? yield footer : footer);
  1038. }
  1039. return dialogWrapperEl;
  1040. });
  1041. }
  1042. };
  1043.  
  1044. // lib/misc.ts
  1045. function autoPlural(word, num) {
  1046. if (Array.isArray(num) || num instanceof NodeList)
  1047. num = num.length;
  1048. return `${word}${num === 1 ? "" : "s"}`;
  1049. }
  1050. function pauseFor(time) {
  1051. return new Promise((res) => {
  1052. setTimeout(() => res(), time);
  1053. });
  1054. }
  1055. function debounce(func, timeout = 300, edge = "falling") {
  1056. let timer;
  1057. return function(...args) {
  1058. if (edge === "rising") {
  1059. if (!timer) {
  1060. func.apply(this, args);
  1061. timer = setTimeout(() => timer = void 0, timeout);
  1062. }
  1063. } else {
  1064. clearTimeout(timer);
  1065. timer = setTimeout(() => func.apply(this, args), timeout);
  1066. }
  1067. };
  1068. }
  1069. function fetchAdvanced(_0) {
  1070. return __async(this, arguments, function* (input, options = {}) {
  1071. const { timeout = 1e4 } = options;
  1072. let signalOpts = {}, id = void 0;
  1073. if (timeout >= 0) {
  1074. const controller = new AbortController();
  1075. id = setTimeout(() => controller.abort(), timeout);
  1076. signalOpts = { signal: controller.signal };
  1077. }
  1078. try {
  1079. const res = yield fetch(input, __spreadValues(__spreadValues({}, options), signalOpts));
  1080. id && clearTimeout(id);
  1081. return res;
  1082. } catch (err) {
  1083. id && clearTimeout(id);
  1084. throw err;
  1085. }
  1086. });
  1087. }
  1088. function insertValues(input, ...values) {
  1089. return input.replace(/%\d/gm, (match) => {
  1090. var _a, _b;
  1091. const argIndex = Number(match.substring(1)) - 1;
  1092. return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? void 0 : _b.toString();
  1093. });
  1094. }
  1095. function compress(input, compressionFormat, outputType = "string") {
  1096. return __async(this, null, function* () {
  1097. const byteArray = typeof input === "string" ? new TextEncoder().encode(input) : input;
  1098. const comp = new CompressionStream(compressionFormat);
  1099. const writer = comp.writable.getWriter();
  1100. writer.write(byteArray);
  1101. writer.close();
  1102. const buf = yield new Response(comp.readable).arrayBuffer();
  1103. return outputType === "arrayBuffer" ? buf : ab2str(buf);
  1104. });
  1105. }
  1106. function decompress(input, compressionFormat, outputType = "string") {
  1107. return __async(this, null, function* () {
  1108. const byteArray = typeof input === "string" ? str2ab(input) : input;
  1109. const decomp = new DecompressionStream(compressionFormat);
  1110. const writer = decomp.writable.getWriter();
  1111. writer.write(byteArray);
  1112. writer.close();
  1113. const buf = yield new Response(decomp.readable).arrayBuffer();
  1114. return outputType === "arrayBuffer" ? buf : new TextDecoder().decode(buf);
  1115. });
  1116. }
  1117. function ab2str(buf) {
  1118. return getUnsafeWindow().btoa(
  1119. new Uint8Array(buf).reduce((data, byte) => data + String.fromCharCode(byte), "")
  1120. );
  1121. }
  1122. function str2ab(str) {
  1123. return Uint8Array.from(getUnsafeWindow().atob(str), (c) => c.charCodeAt(0));
  1124. }
  1125. function computeHash(input, algorithm = "SHA-256") {
  1126. return __async(this, null, function* () {
  1127. let data;
  1128. if (typeof input === "string") {
  1129. const encoder = new TextEncoder();
  1130. data = encoder.encode(input);
  1131. } else
  1132. data = input;
  1133. const hashBuffer = yield crypto.subtle.digest(algorithm, data);
  1134. const hashArray = Array.from(new Uint8Array(hashBuffer));
  1135. const hashHex = hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
  1136. return hashHex;
  1137. });
  1138. }
  1139. function randomId(length = 16, radix = 16, enhancedEntropy = false) {
  1140. if (enhancedEntropy) {
  1141. const arr = new Uint8Array(length);
  1142. crypto.getRandomValues(arr);
  1143. return Array.from(
  1144. arr,
  1145. (v) => mapRange(v, 0, 255, 0, radix).toString(radix).substring(0, 1)
  1146. ).join("");
  1147. }
  1148. return Array.from(
  1149. { length },
  1150. () => Math.floor(Math.random() * radix).toString(radix)
  1151. ).join("");
  1152. }
  1153.  
  1154. // lib/SelectorObserver.ts
  1155. var domLoaded = false;
  1156. document.addEventListener("DOMContentLoaded", () => domLoaded = true);
  1157. var SelectorObserver = class {
  1158. constructor(baseElement, options = {}) {
  1159. __publicField(this, "enabled", false);
  1160. __publicField(this, "baseElement");
  1161. __publicField(this, "observer");
  1162. __publicField(this, "observerOptions");
  1163. __publicField(this, "customOptions");
  1164. __publicField(this, "listenerMap");
  1165. this.baseElement = baseElement;
  1166. this.listenerMap = /* @__PURE__ */ new Map();
  1167. const _a = options, {
  1168. defaultDebounce,
  1169. defaultDebounceEdge,
  1170. disableOnNoListeners,
  1171. enableOnAddListener
  1172. } = _a, observerOptions = __objRest(_a, [
  1173. "defaultDebounce",
  1174. "defaultDebounceEdge",
  1175. "disableOnNoListeners",
  1176. "enableOnAddListener"
  1177. ]);
  1178. this.observerOptions = __spreadValues({
  1179. childList: true,
  1180. subtree: true
  1181. }, observerOptions);
  1182. this.customOptions = {
  1183. defaultDebounce: defaultDebounce != null ? defaultDebounce : 0,
  1184. defaultDebounceEdge: defaultDebounceEdge != null ? defaultDebounceEdge : "rising",
  1185. disableOnNoListeners: disableOnNoListeners != null ? disableOnNoListeners : false,
  1186. enableOnAddListener: enableOnAddListener != null ? enableOnAddListener : true
  1187. };
  1188. if (typeof this.customOptions.checkInterval !== "number") {
  1189. this.observer = new MutationObserver(() => this.checkAllSelectors());
  1190. } else {
  1191. this.checkAllSelectors();
  1192. setInterval(() => this.checkAllSelectors(), this.customOptions.checkInterval);
  1193. }
  1194. }
  1195. /** Call to check all selectors in the {@linkcode listenerMap} using {@linkcode checkSelector()} */
  1196. checkAllSelectors() {
  1197. if (!this.enabled || !domLoaded)
  1198. return;
  1199. for (const [selector, listeners] of this.listenerMap.entries())
  1200. this.checkSelector(selector, listeners);
  1201. }
  1202. /** Checks if the element(s) with the given {@linkcode selector} exist in the DOM and calls the respective {@linkcode listeners} accordingly */
  1203. checkSelector(selector, listeners) {
  1204. var _a;
  1205. if (!this.enabled)
  1206. return;
  1207. const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement;
  1208. if (!baseElement)
  1209. return;
  1210. const all = listeners.some((listener) => listener.all);
  1211. const one = listeners.some((listener) => !listener.all);
  1212. const allElements = all ? baseElement.querySelectorAll(selector) : null;
  1213. const oneElement = one ? baseElement.querySelector(selector) : null;
  1214. for (const options of listeners) {
  1215. if (options.all) {
  1216. if (allElements && allElements.length > 0) {
  1217. options.listener(allElements);
  1218. if (!options.continuous)
  1219. this.removeListener(selector, options);
  1220. }
  1221. } else {
  1222. if (oneElement) {
  1223. options.listener(oneElement);
  1224. if (!options.continuous)
  1225. this.removeListener(selector, options);
  1226. }
  1227. }
  1228. if (((_a = this.listenerMap.get(selector)) == null ? void 0 : _a.length) === 0)
  1229. this.listenerMap.delete(selector);
  1230. if (this.listenerMap.size === 0 && this.customOptions.disableOnNoListeners)
  1231. this.disable();
  1232. }
  1233. }
  1234. /**
  1235. * Starts observing the children of the base element for changes to the given {@linkcode selector} according to the set {@linkcode options}
  1236. * @param selector The selector to observe
  1237. * @param options Options for the selector observation
  1238. * @param options.listener Gets called whenever the selector was found in the DOM
  1239. * @param [options.all] Whether to use `querySelectorAll()` instead - default is false
  1240. * @param [options.continuous] Whether to call the listener continuously instead of just once - default is false
  1241. * @param [options.debounce] Whether to debounce the listener to reduce calls to `querySelector` or `querySelectorAll` - set undefined or <=0 to disable (default)
  1242. * @returns Returns a function that can be called to remove this listener more easily
  1243. */
  1244. addListener(selector, options) {
  1245. options = __spreadValues({
  1246. all: false,
  1247. continuous: false,
  1248. debounce: 0
  1249. }, options);
  1250. if (options.debounce && options.debounce > 0 || this.customOptions.defaultDebounce && this.customOptions.defaultDebounce > 0) {
  1251. options.listener = debounce(
  1252. options.listener,
  1253. options.debounce || this.customOptions.defaultDebounce,
  1254. options.debounceEdge || this.customOptions.defaultDebounceEdge
  1255. );
  1256. }
  1257. if (this.listenerMap.has(selector))
  1258. this.listenerMap.get(selector).push(options);
  1259. else
  1260. this.listenerMap.set(selector, [options]);
  1261. if (this.enabled === false && this.customOptions.enableOnAddListener)
  1262. this.enable();
  1263. this.checkSelector(selector, [options]);
  1264. return () => this.removeListener(selector, options);
  1265. }
  1266. /** Disables the observation of the child elements */
  1267. disable() {
  1268. var _a;
  1269. if (!this.enabled)
  1270. return;
  1271. this.enabled = false;
  1272. (_a = this.observer) == null ? void 0 : _a.disconnect();
  1273. }
  1274. /**
  1275. * Enables or reenables the observation of the child elements.
  1276. * @param immediatelyCheckSelectors Whether to immediately check if all previously registered selectors exist (default is true)
  1277. * @returns Returns true when the observation was enabled, false otherwise (e.g. when the base element wasn't found)
  1278. */
  1279. enable(immediatelyCheckSelectors = true) {
  1280. var _a;
  1281. const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement;
  1282. if (this.enabled || !baseElement)
  1283. return false;
  1284. this.enabled = true;
  1285. (_a = this.observer) == null ? void 0 : _a.observe(baseElement, this.observerOptions);
  1286. if (immediatelyCheckSelectors)
  1287. this.checkAllSelectors();
  1288. return true;
  1289. }
  1290. /** Returns whether the observation of the child elements is currently enabled */
  1291. isEnabled() {
  1292. return this.enabled;
  1293. }
  1294. /** Removes all listeners that have been registered with {@linkcode addListener()} */
  1295. clearListeners() {
  1296. this.listenerMap.clear();
  1297. }
  1298. /**
  1299. * Removes all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()}
  1300. * @returns Returns true when all listeners for the associated selector were found and removed, false otherwise
  1301. */
  1302. removeAllListeners(selector) {
  1303. return this.listenerMap.delete(selector);
  1304. }
  1305. /**
  1306. * Removes a single listener for the given {@linkcode selector} and {@linkcode options} that has been registered with {@linkcode addListener()}
  1307. * @returns Returns true when the listener was found and removed, false otherwise
  1308. */
  1309. removeListener(selector, options) {
  1310. const listeners = this.listenerMap.get(selector);
  1311. if (!listeners)
  1312. return false;
  1313. const index = listeners.indexOf(options);
  1314. if (index > -1) {
  1315. listeners.splice(index, 1);
  1316. return true;
  1317. }
  1318. return false;
  1319. }
  1320. /** Returns all listeners that have been registered with {@linkcode addListener()} */
  1321. getAllListeners() {
  1322. return this.listenerMap;
  1323. }
  1324. /** Returns all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()} */
  1325. getListeners(selector) {
  1326. return this.listenerMap.get(selector);
  1327. }
  1328. };
  1329.  
  1330. // lib/translation.ts
  1331. var trans = {};
  1332. var curLang;
  1333. function tr(key, ...args) {
  1334. var _a;
  1335. if (!curLang)
  1336. return key;
  1337. const trText = (_a = trans[curLang]) == null ? void 0 : _a[key];
  1338. if (!trText)
  1339. return key;
  1340. if (args.length > 0 && trText.match(/%\d/)) {
  1341. return insertValues(trText, ...args);
  1342. }
  1343. return trText;
  1344. }
  1345. tr.addLanguage = (language, translations) => {
  1346. trans[language] = translations;
  1347. };
  1348. tr.setLanguage = (language) => {
  1349. curLang = language;
  1350. };
  1351. tr.getLanguage = () => {
  1352. return curLang;
  1353. };
  1354.  
  1355. exports.DataStore = DataStore;
  1356. exports.DataStoreSerializer = DataStoreSerializer;
  1357. exports.Dialog = Dialog;
  1358. exports.NanoEmitter = NanoEmitter;
  1359. exports.SelectorObserver = SelectorObserver;
  1360. exports.addGlobalStyle = addGlobalStyle;
  1361. exports.addParent = addParent;
  1362. exports.autoPlural = autoPlural;
  1363. exports.clamp = clamp;
  1364. exports.compress = compress;
  1365. exports.computeHash = computeHash;
  1366. exports.debounce = debounce;
  1367. exports.decompress = decompress;
  1368. exports.defaultDialogCss = defaultDialogCss;
  1369. exports.defaultStrings = defaultStrings;
  1370. exports.fetchAdvanced = fetchAdvanced;
  1371. exports.getSiblingsFrame = getSiblingsFrame;
  1372. exports.getUnsafeWindow = getUnsafeWindow;
  1373. exports.insertValues = insertValues;
  1374. exports.interceptEvent = interceptEvent;
  1375. exports.interceptWindowEvent = interceptWindowEvent;
  1376. exports.isScrollable = isScrollable;
  1377. exports.mapRange = mapRange;
  1378. exports.observeElementProp = observeElementProp;
  1379. exports.openDialogs = openDialogs;
  1380. exports.openInNewTab = openInNewTab;
  1381. exports.pauseFor = pauseFor;
  1382. exports.preloadImages = preloadImages;
  1383. exports.randRange = randRange;
  1384. exports.randomId = randomId;
  1385. exports.randomItem = randomItem;
  1386. exports.randomItemIndex = randomItemIndex;
  1387. exports.randomizeArray = randomizeArray;
  1388. exports.takeRandomItem = takeRandomItem;
  1389. exports.tr = tr;
  1390.  
  1391. return exports;
  1392.  
  1393. })({});