JSPM

  • Created
  • Published
  • Downloads 120
  • Score
    100M100P100Q90204F
  • License MIT

Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more

Package Exports

  • @sv443-network/userutils
  • @sv443-network/userutils/dist/index.js
  • @sv443-network/userutils/dist/index.mjs

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@sv443-network/userutils) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

UserUtils

Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more.
Contains builtin TypeScript declarations. Webpack compatible and supports ESM and CJS.


Table of Contents:



Installation:

  • If you are using a bundler like webpack, you can install this package using npm:
    npm i @sv443-network/userutils
    Then, import it in your script as usual:
    import { addGlobalStyle } from "@sv443-network/userutils";
    // or
    import * as userUtils from "@sv443-network/userutils";
    Shameless plug: I also have a webpack-based template for userscripts in TypeScript that you can use to get started quickly.

  • If you are not using a bundler, you can include the latest release from GreasyFork by adding this directive to the userscript header:
    // @require https://greasyfork.org/scripts/TODO

If you like using this library, please consider supporting development



Features:

DOM:

onSelector()

Usage:

onSelector<TElement = HTMLElement>(selector: string, options: {
  listener: (elements: TElement | NodeListOf<TElement>) => void,
  all?: boolean,
  continuous?: boolean,
}): void

Registers a listener to be called whenever the element(s) behind a selector is/are found in the DOM.
If the selector already exists, the listener will be called immediately.

If all is set to true, querySelectorAll() will be used instead and the listener will return a NodeList of matching elements.
This will also include elements that were already found in a previous listener call.
If set to false (default), querySelector() will be used and only the first matching element will be returned.

If continuous is set to true, the listener will not be deregistered after it was called once (defaults to false).

When using TypeScript, the generic TElement can be used to specify the type of the element(s) that the listener will return.

⚠️ In order to use this function, initOnSelector() has to be called as soon as possible.
This initialization function has to be called after DOMContentLoaded is fired (or immediately if @run-at document-end is set).

Calling onSelector() before DOMContentLoaded is fired will not throw an error, but it also won't trigger listeners until the DOM is accessible.

Example - click to view
document.addEventListener("DOMContentLoaded", initOnSelector);

// Continuously checks if `div` elements are added to the DOM, then returns all of them (even previously detected ones) in a NodeList
onSelector<HTMLDivElement>("div", {
  listener: (elements) => {
    console.log("Elements found:", elements); // type = NodeListOf<HTMLDivElement>
  },
  all: true,
  continuous: true,
});

// Checks if an input element with a value attribute of "5" is added to the DOM, then returns it and deregisters the listener
onSelector<HTMLInputElement>("input[value=\"5\"]", {
  listener: (element) => {
    console.log("Element found:", element); // type = HTMLInputElement
  },
});

initOnSelector()

Usage:

initOnSelector(options?: {
  attributes?: boolean,
  characterData?: boolean,
}): void

Initializes the MutationObserver that is used by onSelector() to check for the registered selectors whenever a DOM change occurs on the <body>
By default, this only checks if elements are added or removed (at any depth).

Set attributes to true to also check for attribute changes on every single descendant of the <body> (defaults to false).
Set characterData to true to also check for character data changes on every single descendant of the <body> (defaults to false).

⚠️ Using these extra options can have a performance impact on larger sites or sites with a constantly changing DOM.

⚠️ This function needs to be run after the DOM has loaded (when using @run-at document-end or after DOMContentLoaded has fired).

Example - click to view
document.addEventListener("DOMContentLoaded", () => {
  initOnSelector({
    attributes: true,
    characterData: true,
  });
});

getSelectorMap()

Usage: getSelectorMap(): Map<string, OnSelectorOptions[]>

Returns a Map of all currently registered selectors and their options, including listener function.
Since multiple listeners can be registered for the same selector, the value of the Map is an array of OnSelectorOptions objects.

Example - click to view
document.addEventListener("DOMContentLoaded", initOnSelector);

onSelector<HTMLDivElement>("div", {
  listener: (elements) => void 0,
  all: true,
  continuous: true,
});

onSelector<HTMLDivElement>("div", {
  listener: (elements) => void 0,
});

const selectorMap = getSelectorMap();
// Map(1) {
//   "div" => [
//     {
//       listener: (elements) => void 0,
//       all: true,
//       continuous: true,
//     },
//     {
//       listener: (elements) => void 0,
//     },
//   ]
// }

getUnsafeWindow()

Usage: getUnsafeWindow(): Window

Returns the unsafeWindow object or falls back to the regular window object if the @grant unsafeWindow is not given.
Userscripts are sandboxed and do not have access to the regular window object, so this function is useful for websites that reject some events that were dispatched by the userscript.

Example - click to view
// trick the site into thinking the mouse was moved:
const mouseEvent = new MouseEvent("mousemove", {
  view: getUnsafeWindow(),
  screenY: 69,
  screenX: 420,
  movementX: 10,
  movementY: 0,
});
document.body.dispatchEvent(mouseEvent);

insertAfter()

Usage: insertAfter(beforeElement: HTMLElement, afterElement: HTMLElement): HTMLElement

Inserts the element passed as afterElement as a sibling after the passed beforeElement.
The passed afterElement will be returned.

⚠️ This function needs to be run after the DOM has loaded (when using @run-at document-end or after DOMContentLoaded has fired).

Example - click to view
// insert a <div> as a sibling next to an element
const beforeElement = document.querySelector("#before");
const afterElement = document.createElement("div");
afterElement.innerText = "After";
insertAfter(beforeElement, afterElement);

addParent()

Usage: addParent(element: HTMLElement, newParent: HTMLElement): HTMLElement

Adds a parent element around the passed element and returns the new parent.
Previously registered event listeners are kept intact.

⚠️ This function needs to be run after the DOM has loaded (when using @run-at document-end or after DOMContentLoaded has fired).

Example - click to view
// add an <a> around an element
const element = document.querySelector("#element");
const newParent = document.createElement("a");
newParent.href = "https://example.org/";
addParent(element, newParent);

addGlobalStyle()

Usage: addGlobalStyle(css: string): void

Adds a global style to the page in form of a <style> element that's inserted into the <head>.
⚠️ This function needs to be run after the DOM has loaded (when using @run-at document-end or after DOMContentLoaded has fired).

Example - click to view
document.addEventListener("DOMContentLoaded", () => {
  addGlobalStyle(`
    body {
      background-color: red;
    }
  `);
});

preloadImages()

Usage: preloadImages(urls: string[], rejects?: boolean): Promise<void>

Preloads images into browser cache by creating an invisible <img> element for each URL passed.
The images will be loaded in parallel and the returned Promise will only resolve once all images have been loaded.
The resulting PromiseSettledResult array will contain the image elements if resolved, or an ErrorEvent if rejected, but only if rejects is set to true.

Example - click to view
preloadImages([
  "https://example.org/image1.png",
  "https://example.org/image2.png",
  "https://example.org/image3.png",
], true)
  .then((results) => {
    console.log("Images preloaded. Results:", results);
  });

openInNewTab()

Usage: openInNewTab(url: string): void

Creates an invisible anchor with a _blank target and clicks it.
Contrary to window.open(), this has a lesser chance to get blocked by the browser's popup blocker and doesn't open the URL as a new window.
This function has to be run in relatively quick succession in response to a user interaction event, else the browser might reject it.

⚠️ This function needs to be run after the DOM has loaded (when using @run-at document-end or after DOMContentLoaded has fired).

Example - click to view
document.querySelector("#my-button").addEventListener("click", () => {
  openInNewTab("https://example.org/");
});

interceptEvent()

Usage: interceptEvent(eventObject: EventTarget, eventName: string, predicate: () => boolean): void

Intercepts all events dispatched on the eventObject and prevents the listeners from being called as long as the predicate function returns a truthy value.
Calling this function will set the Error.stackTraceLimit to 1000 (if it's not already higher) to ensure the stack trace is preserved.

⚠️ This function should be called as soon as possible (I recommend using @run-at document-start), as it will only intercept events that are attached after this function is called.

Example - click to view
interceptEvent(document.body, "click", () => {
  return true; // prevent all click events on the body element
});

interceptWindowEvent()

Usage: interceptWindowEvent(eventName: string, predicate: () => boolean): void

Intercepts all events dispatched on the window object and prevents the listeners from being called as long as the predicate function returns a truthy value.
This is essentially the same as interceptEvent(), but automatically uses the unsafeWindow (or falls back to regular window).

⚠️ This function should be called as soon as possible (I recommend using @run-at document-start), as it will only intercept events that are attached after this function is called.

Example - click to view
interceptWindowEvent("beforeunload", () => {
  return true; // prevent the pesky "Are you sure you want to leave this page?" popup
});



Math:

clamp()

Usage: clamp(num: number, min: number, max: number): number

Clamps a number between a min and max value.

Example - click to view
clamp(5, 0, 10);        // 5
clamp(-1, 0, 10);       // 0
clamp(7, 0, 10);        // 7
clamp(Infinity, 0, 10); // 10

mapRange()

Usage: mapRange(value: number, range_1_min: number, range_1_max: number, range_2_min: number, range_2_max: number): number

Maps a number from one range to the spot it would be in another range.

Example - click to view
mapRange(5, 0, 10, 0, 100); // 50
mapRange(5, 0, 10, 0, 50);  // 25
// to calculate a percentage from arbitrary values, use 0 and 100 as the second range:
mapRange(4, 0, 13, 0, 100); // 30.76923076923077

randRange()

Usages:

randRange(min: number, max: number): number
randRange(max: number): number

Returns a random number between min and max (inclusive).
If only one argument is passed, it will be used as the max value and min will be set to 0.

Example - click to view
randRange(0, 10);  // 4
randRange(10, 20); // 17
randRange(10);     // 7



Misc:

autoPlural()

Usage: autoPlural(str: string, num: number | Array | NodeList): string

Automatically pluralizes a string if the given number is not 1.
If an array or NodeList is passed, the length of it will be used.

Example - click to view
autoPlural("apple", 0); // "apples"
autoPlural("apple", 1); // "apple"
autoPlural("apple", 2); // "apples"

autoPlural("apple", [1]);    // "apple"
autoPlural("apple", [1, 2]); // "apples"

const items = [1, 2, 3, 4, "foo", "bar"];
console.log(`Found ${items.length} ${autoPlural("item", items)}`); // "Found 6 items"

pauseFor()

Usage: pauseFor(ms: number): Promise<void>

Pauses async execution for a given amount of time.

Example - click to view
async function run() {
  console.log("Hello");
  await pauseFor(3000); // waits for 3 seconds
  console.log("World");
}

debounce()

Usage: debounce(func: Function, timeout?: number): Function

Debounces a function, meaning that it will only be called once after a given amount of time.
This is very useful for functions that are called repeatedly, like event listeners, to remove extraneous calls.
The timeout will default to 300ms if left undefined.

Example - click to view
window.addEventListener("resize", debounce((event) => {
  console.log("Window was resized:", event);
}, 500)); // 500ms timeout

fetchAdvanced()

Usage:

fetchAdvanced(url: string, options?: {
  timeout?: number,
  // any other options from fetch() except for signal
}): Promise<Response>

A wrapper around the native fetch() function that adds options like a timeout property.
The timeout will default to 10 seconds if left undefined.

Example - click to view
fetchAdvanced("https://api.example.org/data", {
  timeout: 5000,
  // also accepts any other fetch options like headers:
  headers: {
    "Accept": "application/json",
  },
}).then(async (response) => {
  console.log("Data:", await response.json());
});



Arrays:

randomItem()

Usage: randomItem(array: Array): any

Returns a random item from an array.
Returns undefined if the array is empty.

Example - click to view
randomItem(["foo", "bar", "baz"]); // "bar"
randomItem([ ]);                   // undefined

randomItemIndex()

Usage: randomItemIndex(array: Array): [item: any, index: number]

Returns a tuple of a random item and its index from an array.
If the array is empty, it will return undefined for both values.

Example - click to view
randomItemIndex(["foo", "bar", "baz"]); // ["bar", 1]
randomItemIndex([ ]);                   // [undefined, undefined]
// using array destructuring:
const [item, index] = randomItemIndex(["foo", "bar", "baz"]);
// or if you only want the index:
const [, index] = randomItemIndex(["foo", "bar", "baz"]);

takeRandomItem()

Usage: takeRandomItem(array: Array): any

Returns a random item from an array and mutates the array by removing the item.
Returns undefined if the array is empty.

Example - click to view
const arr = ["foo", "bar", "baz"];
takeRandomItem(arr); // "bar"
console.log(arr);    // ["foo", "baz"]

randomizeArray()

Usage: randomizeArray(array: Array): Array

Returns a copy of the array with its items in a random order.
If the array is empty, the originally passed array will be returned.

Example - click to view
randomizeArray([1, 2, 3, 4, 5, 6]); // [3, 1, 5, 2, 4, 6]



License:

This library is licensed under the MIT License.
See the license file for details.



Made with ❤️ by Sv443
If you like this library, please consider supporting development