General purpose DOM/GreaseMonkey library that allows you to register listeners for when CSS selectors exist, intercept events, create persistent & synchronous data stores, modify the DOM more easily and much more.
Contains builtin TypeScript declarations. Supports ESM and CJS imports via a bundler and global declaration via @require or <script>
The library works in any DOM environment with or without the GreaseMonkey API, but some features will be unavailable or limited.
You may want to check out my template for userscripts in TypeScript that you can use to get started quickly. It also includes this library by default.
If you like using this library, please consider supporting the development ❤️
This library is written in TypeScript with builtin TypeScript declarations, but it works just as well in plain JavaScript after removing the : type annotations from the example code snippets.
The library supports importing an ESM, CommonJS or global variable definition bundle, depending on your use case.
The signatures and example snippets use TypeScript with ESM import syntax to show which types need to be provided and will be returned.
If the signature section contains multiple signatures, each one represents an overload. They will be further explained in the description below that section.
Each feature's example code snippet can be expanded by clicking on the text ▷ Example - click to view below its description.
Some features require the @run-at or @grant directives to be tweaked in the userscript header, or have other specific requirements and limitations. These will be listed in a section marked by a warning emoji (
Note
In version 10.0.0, many of the platform-agnostic features were moved to the CoreUtils library.
Everything in CoreUtils is re-exported by UserUtils for backwards compatibility, so installing both at the same time isn't usually necessary.
Beware that when both are installed, class inheritance between the two libraries will only work if the installed version of CoreUtils matches the version of CoreUtils that is included in UserUtils (refer to package.json), so that the final bundler is able to deduplicate them correctly. See also const versions
Tip
If you need help with something, please create a new discussion or join my Discord server.
For bug reports or feature requests, please use the GitHub issue tracker.
- Preamble (info about the documentation)
- UserUtils Features
- DOM:
- 🟧
class Dialog- class for creating custom modal dialogs with a promise-based API and a generic, default style - 🟧
class SelectorObserver- class that manages listeners that are called when selectors are found in the DOM - 🟣
function getUnsafeWindow()- get the unsafeWindow object or fall back to the regular window object - 🟣
function isDomLoaded()- check if the DOM has finished loading and can be queried and modified - 🟣
function onDomLoad()- run a function or pause async execution until the DOM has finished loading (or immediately if DOM is already loaded) - 🟣
function addParent()- add a parent element around another element - 🟣
function addGlobalStyle()- add a global style to the page - 🟣
function preloadImages()- preload images into the browser cache for faster loading later on - 🟣
function openInNewTab()- open a link in a new tab - 🟣
function interceptEvent()- conditionally intercepts events registered byaddEventListener()on any given EventTarget object - 🟣
function interceptWindowEvent()- conditionally intercepts events registered byaddEventListener()on the window object - 🟣
function isScrollable()- check if an element has a horizontal or vertical scroll bar - 🟣
function observeElementProp()- observe changes to an element's property that can't be observed with MutationObserver - 🟣
function getSiblingsFrame()- returns a frame of an element's siblings, with a given alignment and size - 🟣
function setInnerHtmlUnsafe()- set the innerHTML of an element using a Trusted Types policy without sanitizing or escaping it - 🟣
function probeElementStyle()- probe the computed style of a temporary element (get default font size, resolve CSS variables, etc.)
- 🟧
- Misc:
- 🟧
class GMStorageEngine- storage engine class forDataStores using the GreaseMonkey API - 🟧
class Mixins- class for creating mixin functions that allow multiple sources to modify a target value in a highly flexible way - 🟩
const versions- contains version information for UserUtils and CoreUtils
- 🟧
- Translation:
- 🟣
function tr.for()- translates a key for the specified language - 🟣
function tr.use()- creates a translation function for the specified language - 🟣
function tr.hasKey()- checks if a key exists in the given language - 🟣
function tr.addTranslations()- add a flat or recursive translation object for a language - 🟣
function tr.getTranslations()- returns the translation object for a language - 🟣
function tr.getAllTranslations()- returns all registered translations - 🟣
function tr.deleteTranslations()- delete the translation object for a language - 🟣
function tr.setFallbackLanguage()- set the fallback language used when a key is not found in the given language - 🟣
function tr.getFallbackLanguage()- returns the fallback language - 🟣
function tr.addTransform()- adds a transform function to the translation system for custom argument interpolation and much more - 🟣
function tr.deleteTransform()- removes a transform function - 🟩
const tr.transforms- predefined transform functions for quickly adding custom argument interpolation - 🔷
type TrKeys- generic type that extracts all keys from a flat or recursive translation object into a union
- 🟣
- Custom Error classes
- 🟧
class PlatformError- thrown when the current platform doesn't support a certain feature, like calling a DOM function in a non-DOM environment
- 🟧
- DOM:
- CoreUtils Features (re-exported for backwards compatibility)
- Array:
- 🟣
function randomItem()- Returns a random item from the given array - 🟣
function randomItemIndex()- Returns a random array item and index as a tuple - 🟣
function randomizeArray()- Returns a new array with the items in random order - 🟣
function takeRandomItem()- Returns a random array item and mutates the array to remove it - 🟣
function takeRandomItemIndex()- Returns a random array item and index as a tuple and mutates the array to remove it - 🔷
type NonEmptyArray- Non-empty array type
- 🟣
- Colors:
- 🟣
function darkenColor()- Darkens the given color by the given percentage - 🟣
function hexToRgb()- Converts a hex color string to an RGB object - 🟣
function lightenColor()- Lightens the given color by the given percentage - 🟣
function rgbToHex()- Converts an RGB object to a hex color string
- 🟣
- Crypto:
- 🟣
function abtoa()- Converts an ArrayBuffer to a string - 🟣
function atoab()- Converts a string to an ArrayBuffer - 🟣
function compress()- Compresses the given string using the given algorithm and encoding - 🟣
function decompress()- Decompresses the given string using the given algorithm and encoding - 🟣
function computeHash()- Computes a string's hash using the given algorithm - 🟣
function randomId()- Generates a random ID of the given length
- 🟣
- DataStore: - Cross-platform, general-purpose, sync/async hybrid, JSON-serializable database infrastructure:
- 🟧
class DataStore- The main class for the data store- 🔷
type DataStoreOptions- Options for the data store - 🔷
type DataMigrationsDict- Dictionary of data migration functions
- 🔷
- 🟧
class DataStoreSerializer- Serializes and deserializes data for multiple DataStore instances- 🔷
type DataStoreSerializerOptions- Options for the DataStoreSerializer - 🔷
type LoadStoresDataResult- Result of callingloadStoresData() - 🔷
type SerializedDataStore- Meta object and serialized data of a DataStore instance - 🔷
type StoreFilter- Filter for selecting data stores
- 🔷
- 🟧
class DataStoreEngine- Base class for DataStore storage engines, which handle the data storage- 🔷
type DataStoreEngineDSOptions- Reduced version ofDataStoreOptions
- 🔷
- Storage Engines:
- 🟧
class BrowserStorageEngine- Storage engine for browser environments (localStorage, sessionStorage)- 🔷
type BrowserStorageEngineOptions- Options for the browser storage engine
- 🔷
- 🟧
class FileStorageEngine- File-based storage engine for Node.js and Deno- 🔷
type FileStorageEngineOptions- Options for the file storage engine
- 🔷
- 🟧
- 🟧
- Debouncer:
- 🟣
function debounce()- Function wrapper for theDebouncerclass - 🟧
class Debouncer- Class that manages listeners whose calls are rate-limited- 🔷
type DebouncerType- The triggering type for the debouncer - 🔷
type DebouncedFunction- Function type that is returned by thedebounce()function - 🔷
type DebouncerEventMap- Event map type for theDebouncerclass
- 🔷
- 🟣
- Errors:
- 🟧
class DatedError- Base error class with adateproperty- 🟧
class ChecksumMismatchError- Error thrown when two checksums don't match - 🟧
class CustomError- Custom error with a configurable name for one-off situations - 🟧
class MigrationError- Error thrown in a failed data migration - 🟧
class ValidationError- Error while validating data
- 🟧
- 🟧
- Math:
- 🟣
function bitSetHas()- Checks if a bit is set in a bitset - 🟣
function clamp()- Clamps a number between a given range - 🟣
function digitCount()- Returns the number of digits in a number - 🟣
function formatNumber()- Formats a number to a string using the given locale and format identifier- 🔷
type NumberFormat- Number format identifier
- 🔷
- 🟣
function mapRange()- Maps a number from one range to another - 🟣
function overflowVal()- Makes sure a number is in a range by over- & underflowing it - 🟣
function randRange()- Returns a random number in the given range - 🟣
function roundFixed()- Rounds the given number to the given number of decimal places - 🟣
function valsWithin()- Checks if the given numbers are within a certain range of each other
- 🟣
- Misc:
- 🟣
function consumeGen()- Consumes aValueGenobject- 🔷
type ValueGen- A value that can be either type T, or a sync or async function that returns T
- 🔷
- 🟣
function consumeStringGen()- Consumes aStringGenobject- 🔷
type StringGen- A value that can be either of type string, or a sync or async function that returns a string
- 🔷
- 🟣
function fetchAdvanced()- Wrapper aroundfetch()with options like a timeout- 🔷
type FetchAdvancedOpts- Options for thefetchAdvanced()function
- 🔷
- 🟣
function getListLength()- Returns the length of aListLikeobject - 🟣
function pauseFor()- Pauses async execution for the given amount of time - 🟣
function pureObj()- Applies an object's props to a null object (object without prototype chain) or just returns a new null object - 🟣
function setImmediateInterval()- LikesetInterval(), but instantly calls the callback and supports passing anAbortSignal - 🟣
function setImmediateTimeoutLoop()- Like a recursivesetTimeout()loop, but instantly calls the callback and supports passing anAbortSignal - 🟣
function createRecurringTask()- Similar tosetImmediateTimeoutLoop(), but with many more ways of controlling execution.- 🔷
type RecurringTaskOptions- Options for thecreateRecurringTask()function.
- 🔷
- 🟣
function scheduleExit()- Schedules a process exit after the next event loop tick, to allow operations like IO writes to finish. - 🟣
function getCallStack()- Returns the current call stack, starting at the caller of this function.
- 🟣
- NanoEmitter:
- 🟧
class NanoEmitter- Simple, lightweight event emitter class that can be used in both FP and OOP, inspired byEventEmitterfromnode:events, based onnanoevents- 🔷
type NanoEmitterOptions- Options for theNanoEmitterclass
- 🔷
- 🟧
- Text:
- 🟣
function autoPlural()- Turns the given term into its plural form, depending on the given number or list length - 🟣
function capitalize()- Capitalizes the first letter of the given string - 🟣
function createProgressBar()- Creates a progress bar string with the given percentage and length- 🟩
const defaultPbChars- Default characters for the progress bar - 🔷
type ProgressBarChars- Type for the progress bar characters object
- 🟩
- 🟣
function joinArrayReadable()- Joins the given array into a string, using the given separators and last separator - 🟣
function secsToTimeStr()- Turns the given number of seconds into a string in the format(hh:)mm:sswith intelligent zero-padding - 🟣
function createTable()- Creates an ASCII table string from the given rows- 🟩
const defaultTableLineCharset- Default line characters for the table - 🔷
type TableOptions- Options for thecreateTable()function - 🔷
type TableLineStyle- The line style to use for the table border - 🔷
type TableColumnAlign- The alignment mode for a column - 🔷
type TableLineCharset- The full charset used for table line characters - 🔷
type TableLineStyleChars- The characters for one line style variant
- 🟩
- 🟣
function truncStr()- Truncates the given string to the given length
- 🟣
- Misc. Types:
- 🔷
type LooseUnion- A union type that allows for autocomplete suggestions as well as substitutions of the same type - 🔷
type ListLike- Any value with a quantifiablelength,countorsizeproperty - 🔷
type Newable- Any class reference that can be instantiated withnew - 🔷
type NonEmptyArray- Non-empty array type - 🔷
type NonEmptyString- String type with at least one character - 🔷
type NumberFormat- Number format identifier - 🔷
type Prettify- Makes the structure of a type more readable by fully expanding it (recursively) - 🔷
type SerializableVal- Any value that can be serialized to JSON - 🔷
type StringGen- A value that can be either of type string, or a sync or async function that returns a string - 🔷
type ValueGen- A value that can be either the generic type T, or a sync or async function that returns T - 🔷
type Stringifiable- Any value that can be implicitly converted to a string
- 🔷
- Array:
Note
🟣 = function
🟧 = class
🔷 = type
🟩 = const
Signature:
class Dialog extends NanoEmitter;Usage:
const dialog = new Dialog(options: DialogOptions);A class that creates a customizable modal dialog with a title (optional), body and footer (optional).
There are tons of options for customization, like changing the close behavior, translating strings and more.
To see all available options, refer to the DialogOptions type.
⚠️ Each instance should have a unique ID, else the elements will conflict with each other in the DOM.
Example - click to view
import { Dialog } from "@sv443-network/userutils";
const myDialog = new Dialog({
id: "my-unique-dialog-id",
width: 450,
height: 250,
closeOnBgClick: true,
closeOnEscPress: true,
destroyOnClose: false,
unmountOnClose: false,
small: true,
verticalAlign: "top",
renderHeader: () => {
const header = document.createElement("div");
header.textContent = "My Custom Dialog";
return header;
},
renderBody: () => {
const body = document.createElement("div");
body.textContent = "This is the body of the dialog.";
return body;
},
renderFooter: () => {
const footer = document.createElement("div");
const closeButton = document.createElement("button");
closeButton.textContent = "Close";
closeButton.addEventListener("click", () => myDialog.close());
footer.appendChild(closeButton);
return footer;
},
});
// register some event listeners:
myDialog.on("open", () => {
console.log("Dialog opened!");
});
myDialog.on("close", () => {
console.log("Dialog closed!");
});
myDialog.on("destroy", () => {
console.log("Dialog destroyed!");
});
// open the dialog:
await myDialog.open();
// pause async execution until the dialog is closed:
await myDialog.once("close");
// destroy the dialog when done:
myDialog.destroy();The Dialog class inherits from NanoEmitter, so you can use all of its inherited methods to listen to the following events:
| Event | Arguments | Description |
|---|---|---|
close |
- | Emitted just after the dialog is closed |
open |
- | Emitted just after the dialog is opened |
render |
- | Emitted just after the dialog contents are rendered |
clear |
- | Emitted just after the dialog contents are cleared |
destroy |
- | Emitted just after the dialog is destroyed and all listeners are removed |
Signature:
public async mount(): Promise<HTMLElement | void>;Call after DOMContentLoaded to pre-render the dialog and invisibly mount it in the DOM.
Signature:
public unmount(): void;Closes the dialog and clears all its contents (unmounts elements from the DOM) in preparation for a new rendering call.
Signature:
public async remount(): Promise<void>;Clears the DOM of the dialog and then renders it again.
This can be used to call the rendering functions again to update the dialog contents.
Signature:
public async open(e?: MouseEvent | KeyboardEvent): Promise<HTMLElement | void>;Opens the dialog - also mounts it if it hasn't been mounted yet.
Prevents default action and immediate propagation of the passed event.
Signature:
public close(e?: MouseEvent | KeyboardEvent): void;Closes the dialog - prevents default action and immediate propagation of the passed event.
Signature:
public isOpen(): boolean;Returns true if the dialog is currently open.
Signature:
public isMounted(): boolean;Returns true if the dialog is currently mounted.
Signature:
public destroy(): void;Clears the DOM of the dialog and removes all event listeners.
Signature:
public static getCurrentDialogId(): string | null;Returns the ID of the top-most dialog (the dialog that has been opened last).
Signature:
public static getOpenDialogs(): string[];Returns the IDs of all currently open dialogs, top-most first.
The options object for the Dialog class.
These are the properties:
| Property | Type | Description |
|---|---|---|
id |
string |
ID that gets added to child element IDs - has to be unique and conform to HTML ID naming rules! |
width |
number |
Target and max width of the dialog in pixels |
height |
number |
Target and max height of the dialog in pixels |
closeOnBgClick? |
boolean | undefined |
Whether the dialog should close when the background is clicked - defaults to true |
closeOnEscPress? |
boolean | undefined |
Whether the dialog should close when the escape key is pressed - defaults to true |
destroyOnClose? |
boolean | undefined |
Whether the dialog should be destroyed when it's closed - defaults to false |
unmountOnClose? |
boolean | undefined |
Whether the dialog should be unmounted when it's closed - defaults to true - superseded by destroyOnClose |
removeListenersOnDestroy? |
boolean | undefined |
Whether all listeners should be removed when the dialog is destroyed - defaults to true |
small? |
boolean | undefined |
Whether the dialog should have a smaller overall appearance - defaults to false |
verticalAlign? |
"top" | "center" | "bottom" | undefined |
Where to align or anchor the dialog vertically - defaults to "center" |
strings? |
Partial<typeof defaultStrings> | undefined |
Strings used in the dialog (used for translations) - defaults to the default English strings exported as defaultStrings |
dialogCss? |
string | undefined |
CSS to apply to the dialog - defaults to the exported constant defaultDialogCss |
renderBody |
() => HTMLElement | Promise<HTMLElement> |
Called to render the body of the dialog |
renderHeader? |
(() => HTMLElement | Promise<HTMLElement>) | undefined |
Called to render the header of the dialog - leave undefined for a blank header |
renderFooter? |
(() => HTMLElement | Promise<HTMLElement>) | undefined |
Called to render the footer of the dialog - leave undefined for no footer |
renderCloseBtn? |
(() => HTMLElement | Promise<HTMLElement>) | undefined |
Called to render the close button of the dialog - leave undefined for no close button |
Signature:
class SelectorObserver;Usage:
// using a valid, mounted Element as the base element:
new SelectorObserver(baseElement: Element, options?: SelectorObserverConstructorOptions);
// using a selector string to find the base element when needed:
new SelectorObserver(baseElementSelector: string, options?: SelectorObserverConstructorOptions);A class that manages listeners that are called when elements at given CSS selectors are found in the DOM.
It is useful for userscripts that need to wait for elements to be added to the DOM at an indeterminate point in time before they can be interacted with.
By default, it uses the MutationObserver API to observe for any element changes, and as such is highly customizable, but can also be configured to run on a fixed interval.
The constructor takes a baseElement, which is a parent of the elements you want to observe.
If a selector string is passed instead, it will be used to find the element as soon as observation is enabled.
If you want to observe the entire document, you can pass document.body -
The options parameter is optional and will be passed to the MutationObserver that is used internally.
The MutationObserver options present by default are { childList: true, subtree: true } - you may see the MutationObserver.observe() documentation for more information and a list of options.
For example, if you want to trigger the listeners when certain attributes change, pass { attributeFilter: ["class", "data-my-attribute"] }
enable() to actually start observing. This will need to be done after the DOM has loaded (when using @run-at document-end or after DOMContentLoaded has fired) and as soon as the baseElement or baseElementSelector is available.
Example - click to view
import { SelectorObserver } from "@sv443-network/userutils";
// adding a single-shot listener before the element exists:
const fooObserver = new SelectorObserver("body");
fooObserver.addListener("#my-element", {
listener: (element) => {
console.log("Element found:", element);
},
});
document.addEventListener("DOMContentLoaded", () => {
// starting observation after the <body> element is available:
fooObserver.enable();
// adding custom observer options:
const barObserver = new SelectorObserver(document.body, {
// only check if the following attributes change:
attributeFilter: ["class", "style", "data-whatever"],
// debounce all listeners by 100ms unless specified otherwise:
defaultDebounce: 100,
defaultDebounceType: "immediate",
});
barObserver.addListener("#my-element", {
listener: (element) => {
console.log("Element's attributes changed:", element);
},
});
barObserver.enable();
// using custom listener options:
const bazObserver = new SelectorObserver(document.body);
// for TypeScript, specify that input elements are returned by the listener:
const unsubscribe = bazObserver.addListener<HTMLInputElement>("input", {
all: true, // use querySelectorAll() instead of querySelector()
continuous: true, // don't remove the listener after it was called once
debounce: 50, // debounce the listener by 50ms
listener: (elements) => {
// type of `elements` is NodeListOf<HTMLInputElement>
console.log("Input elements found:", elements);
},
});
bazObserver.enable();
window.addEventListener("something", () => {
// remove the listener after the event "something" was dispatched:
unsubscribe();
});
});Signature:
public addListener<TElem extends Element = HTMLElement>(
selector: string,
options: SelectorListenerOptions<TElem>
): UnsubscribeFunction;Starts observing the children of the base element for changes to the given selector according to the set options.
Returns a function that can be called to remove this listener.
The options object has the following properties:
| Property | Type | Description |
|---|---|---|
listener |
(element: TElem) => void or (elements: NodeListOf<TElem>) => void |
Gets called whenever the selector is found in the DOM |
all? |
boolean | undefined |
Whether to use querySelectorAll() instead - defaults to false |
continuous? |
boolean | undefined |
Whether to call the listener continuously instead of just once - defaults to false |
debounce? |
number | undefined |
Whether to debounce the listener to reduce calls - set undefined or <=0 to disable (default) |
debounceType? |
DebouncerType | undefined |
The edge type of the debouncer - defaults to "immediate" |
Signature:
public enable(immediatelyCheckSelectors?: boolean): boolean;Enables or reenables the observation of the child elements.
immediatelyCheckSelectors defaults to true, which means all previously registered selectors will be checked.
Returns true when the observation was enabled, false otherwise (e.g. when the base element wasn't found).
Signature:
public disable(): void;Disables the observation of the child elements.
Signature:
public isEnabled(): boolean;Returns whether the observation of the child elements is currently enabled.
Signature:
public clearListeners(): void;Removes all listeners that have been registered with addListener().
Signature:
public removeAllListeners(selector: string): boolean;Removes all listeners for the given selector.
Returns true when all listeners for the associated selector were found and removed, false otherwise.
Signature:
public removeListener(selector: string, options: SelectorListenerOptions): boolean;Removes a single listener for the given selector and options.
Returns true when the listener was found and removed, false otherwise.
Signature:
public getAllListeners(): Map<string, SelectorListenerOptions<HTMLElement>[]>;Returns all listeners that have been registered with addListener().
Signature:
public getListeners(selector: string): SelectorListenerOptions<HTMLElement>[] | undefined;Returns all listeners for the given selector or undefined if there are none.
Options object passed to the SelectorObserver constructor.
Extends MutationObserverInit with the following additional properties:
| Property | Type | Description |
|---|---|---|
defaultDebounce? |
number | undefined |
If set, applies this debounce in milliseconds to all listeners that don't have their own debounce set |
defaultDebounceType? |
DebouncerType | undefined |
If set, applies this debounce edge type to all listeners that don't have their own set - defaults to "immediate" |
disableOnNoListeners? |
boolean | undefined |
Whether to disable the observer when no listeners are present - defaults to false |
enableOnAddListener? |
boolean | undefined |
Whether to ensure the observer is enabled when a new listener is added - defaults to true |
checkInterval? |
number | undefined |
If set to a number, the checks will be run on interval instead of on mutation events - all MutationObserverInit props will be ignored |
Options object passed to SelectorObserver.addListener().
See the table in that section for more details.
Signature:
function 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, or userscripts that need to interact with other userscripts, and more.
Example - click to view
import { getUnsafeWindow } from "@sv443-network/userutils";
// 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);Signature:
function isDomLoaded(): boolean;Returns whether or not the DOM has finished loading and can be queried and modified.
As long as the library is loaded immediately on page load, this function will always return the correct value, even if your runtime is executed after the DOM has finished loading (like when using @run-at document-end).
Example - click to view
import { isDomLoaded } from "@sv443-network/userutils";
console.log(isDomLoaded()); // false
document.addEventListener("DOMContentLoaded", () => {
console.log(isDomLoaded()); // true
});Signature:
function onDomLoad(cb?: () => void): Promise<void>;Executes a callback and/or resolves the returned Promise when the DOM has finished loading.
Immediately executes/resolves if the DOM is already loaded.
Example - click to view
import { onDomLoad } from "@sv443-network/userutils";
onDomLoad(() => {
console.log("DOM has finished loading.");
});
document.addEventListener("DOMContentLoaded", async () => {
console.log("DOM loaded!");
// immediately resolves because the DOM is already loaded:
await onDomLoad();
console.log("DOM has finished loading.");
});Signature:
function addParent<TElem extends Element, TParentElem extends Element>(
element: TElem,
newParent: TParentElem
): TParentElem;Adds a parent container around the provided element and returns the new parent element.
Previously registered event listeners are kept intact.
@run-at document-end or after DOMContentLoaded has fired).
Example - click to view
import { addParent } from "@sv443-network/userutils";
const element = document.querySelector("#element");
const newParent = document.createElement("a");
newParent.href = "https://example.org/";
addParent(element, newParent);Signature:
function addGlobalStyle(style: string): HTMLStyleElement;Adds global CSS style in the form of a <style> element in the document's <head>.
Returns the created style element.
@run-at document-end or after DOMContentLoaded has fired).
Example - click to view
import { addGlobalStyle } from "@sv443-network/userutils";
document.addEventListener("DOMContentLoaded", () => {
addGlobalStyle(`
body {
background-color: red;
}
`);
});Signature:
function preloadImages(srcUrls: string[], rejects?: boolean): Promise<PromiseSettledResult<HTMLImageElement>[]>;Preloads an array of image URLs so they can be loaded instantly from the browser cache later on.
The rejects parameter defaults to false. If set to true, the returned PromiseSettledResults will contain rejections for any of the images that failed to load.
Each resolved result will contain the loaded image element, while each rejected result will contain an ErrorEvent.
Example - click to view
import { preloadImages } from "@sv443-network/userutils";
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);
});Signature:
function openInNewTab(href: string, background?: boolean, additionalProps?: Partial<HTMLAnchorElement>): void;Tries to use GM.openInTab to open the given URL in a new tab, otherwise if the grant is not given, creates an invisible anchor element and clicks it.
If background is set to true, the tab will be opened in the background. Leave undefined to use the browser's default behavior.
If additionalProps is set and GM.openInTab is not available, the given properties will be added or overwritten on the created anchor element.
@grant GM.openInTab directive, otherwise only the fallback behavior will be used.
Example - click to view
import { openInNewTab } from "@sv443-network/userutils";
document.querySelector("#my-button").addEventListener("click", () => {
openInNewTab("https://example.org/", true);
});Signature:
function interceptEvent<
TEvtObj extends EventTarget,
TPredicateEvt extends Event,
>(
eventObject: TEvtObj,
eventName: Parameters<TEvtObj["addEventListener"]>[0],
predicate?: (event: TPredicateEvt) => boolean
): void;Intercepts the specified event on the passed object and prevents it from being called if the called predicate function returns a truthy value.
If no predicate is specified, all events will be discarded.
Calling this function will set Error.stackTraceLimit = 100 (if not already higher) to ensure the stack trace is preserved.
@run-at document-start), as it will only intercept events that are added after this function is called.
addEventListener prototype, it might break execution of the page's main script if the userscript is running in an isolated context (like it does in FireMonkey). In that case, calling this function will throw a PlatformError.
Example - click to view
import { interceptEvent } from "@sv443-network/userutils";
interceptEvent(document.body, "click", (event) => {
// prevent all click events on <a> elements within the entire <body>
if(event.target instanceof HTMLAnchorElement) {
console.log("Intercepting click event:", event);
return true;
}
return false; // allow all other click events through
});Signature:
function interceptWindowEvent<TEvtKey extends keyof WindowEventMap>(
eventName: TEvtKey,
predicate?: (event: WindowEventMap[TEvtKey]) => boolean
): void;Intercepts the specified event on the unsafeWindow (if available) or window object and prevents it from being called if the called predicate function returns a truthy value.
If no predicate is specified, all events will be discarded.
This is essentially the same as interceptEvent(), but automatically uses the unsafeWindow or window, depending on availability.
@run-at document-start), as it will only intercept events that are added after this function is called.
@grant unsafeWindow should be set.
Example - click to view
import { interceptWindowEvent } from "@sv443-network/userutils";
// prevent the "Are you sure you want to leave this page?" popup:
interceptWindowEvent("beforeunload");
// discard all context menu commands that are not within #my-element:
interceptWindowEvent("contextmenu", (event) =>
event.target instanceof HTMLElement && !event.target.closest("#my-element")
);Signature:
function isScrollable(element: Element): Record<"vertical" | "horizontal", boolean>;Checks if an element has a horizontal or vertical scroll bar.
Uses the computed style of the element, so it has a high chance of working even if the element is hidden.
Example - click to view
import { isScrollable } from "@sv443-network/userutils";
const element = document.querySelector("#element");
const { horizontal, vertical } = isScrollable(element);
console.log("Element has a horizontal scroll bar:", horizontal);
console.log("Element has a vertical scroll bar:", vertical);Signature:
function observeElementProp<
TElem extends Element = HTMLElement,
TPropKey extends keyof TElem = keyof TElem,
>(
element: TElem,
property: TPropKey,
callback: (oldVal: TElem[TPropKey], newVal: TElem[TPropKey]) => void
): void;Executes the callback when the passed element's property changes.
Contrary to an element's attributes, properties can usually not be observed with a MutationObserver.
This function shims the getter and setter of the property to invoke the callback.
When using TypeScript, the types for element, property and the arguments for callback will be automatically inferred.
Example - click to view
import { observeElementProp } from "@sv443-network/userutils";
const myInput = document.querySelector("input#my-input");
observeElementProp(myInput, "value", (oldValue, newValue) => {
console.log("Value changed from", oldValue, "to", newValue);
});Signature:
function getSiblingsFrame<TSibling extends Element = HTMLElement>(
refElement: Element,
siblingAmount: number,
refElementAlignment?: "center-top" | "center-bottom" | "top" | "bottom",
includeRef?: boolean
): TSibling[];Returns a "frame" of the closest siblings of the refElement, based on the passed amount of siblings and refElementAlignment.
These are the parameters:
refElement- The reference element to return the relative closest siblings from.siblingAmount- The amount of siblings to return in total.refElementAlignment- Can be set tocenter-top(default),center-bottom,top, orbottom, which will determine where the relative location of the providedrefElementis in the returned array.includeRef- If set totrue(default), the providedrefElementwill be included in the returned array at its corresponding position.
Example - click to view
import { getSiblingsFrame } from "@sv443-network/userutils";
const refElement = document.querySelector("#ref");
// ^ structure of the elements:
// <div id="parent">
// <div>1</div>
// <div>2</div>
// <div id="ref">3</div>
// <div>4</div>
// <div>5</div>
// <div>6</div>
// </div>
// ref element aligned to the top, included in the result:
const siblings = getSiblingsFrame(refElement, 3, "top", true);
// [<div id="ref">3</div>, <div>4</div>, <div>5</div>]Signature:
function setInnerHtmlUnsafe<TElement extends Element = HTMLElement>(
element: TElement,
html: string
): TElement;Sets the innerHTML property of the provided element without any sanitization or validation.
Uses a Trusted Types policy on Chromium-based browsers to trick the browser into thinking the HTML is safe.
Returns the element that was passed for chaining.
element.textContent = "foo" or other safer alternatives like the DOMPurify library whenever possible.
Example - click to view
import { setInnerHtmlUnsafe } from "@sv443-network/userutils";
const myElement = document.querySelector("#my-element");
setInnerHtmlUnsafe(myElement, "<img src='https://picsum.photos/100/100' />");Signature:
function probeElementStyle<
TValue,
TElem extends HTMLElement = HTMLSpanElement,
>(
probeStyle: (style: CSSStyleDeclaration, element: TElem) => TValue,
element?: TElem | (() => TElem),
hideOffscreen?: boolean,
parentElement?: HTMLElement
): TValue;Creates an invisible temporary element to probe its rendered computed style.
This might be useful for resolving the value behind a CSS variable, getting the browser's default font size, etc.
@run-at document-end or after DOMContentLoaded has fired).
probeStyle- Function to probe the element's style. First argument is the element's style object, second argument is the element itself.element- The element to probe, or a function that creates and returns the element. All probe elements will have the class_uu_probe_elementadded. Defaults to a<span>element.hideOffscreen- Whether to hide the element offscreen (default: true). Disable if you want to probe position style properties.parentElement- The parent element to append the probe element to (default:document.body).
Example - click to view
import { probeElementStyle } from "@sv443-network/userutils";
document.addEventListener("DOMContentLoaded", () => {
const probedCol = probeElementStyle(
(style) => style.backgroundColor,
() => {
const elem = document.createElement("span");
elem.style.backgroundColor = "var(--my-cool-color, #000)";
return elem;
},
true,
);
console.log("Resolved:", probedCol);
});Signature:
class GMStorageEngine<TData extends object> extends DataStoreEngine<TData>;Usage:
const engine = new GMStorageEngine(options?: GMStorageEngineOptions);Storage engine for the DataStore class that uses GreaseMonkey's GM.getValue and GM.setValue functions.
This class can also be used standalone for an abstracted, uniform interface to GM storage, but is primarily intended to be used as a storage engine for DataStore.
Refer to the DataStore documentation for more information on how to use DataStore and storage engines.
⚠️ Requires the grantsGM.getValue,GM.setValue,GM.deleteValue, andGM.listValuesin your userscript metadata.⚠️ To avoid having to specify index signatures for the data type, the template genericTDatais constrained toobject. However, only values serializable byJSON.stringify()and storable in GM storage (e.g. no functions, symbols, BigInts, etc.) are supported.⚠️ Don't reuse engine instances, always create a new one for each DataStore instance.
Example - click to view
import { DataStore, GMStorageEngine } from "@sv443-network/userutils";
const myStore = new DataStore({
id: "my-data",
defaultData: { foo: "bar" },
formatVersion: 1,
engine: new GMStorageEngine(),
});
await myStore.loadData();
console.log(myStore.getData()); // { foo: "bar" }Signature:
public async getValue<TValue extends SerializableVal = string>(name: string, defaultValue: TValue): Promise<string | TValue>;Fetches a value from persistent GM storage.
Signature:
public async setValue<TValue extends SerializableVal = string>(name: string, value: TValue): Promise<void>;Sets a value in persistent GM storage.
Signature:
public async deleteValue(name: string): Promise<void>;Deletes a value from persistent GM storage.
Signature:
public async deleteStorage(): Promise<void>;Deletes all values from the GM storage.
Options for the GMStorageEngine class.
| Property | Type | Description |
|---|---|---|
dataStoreOptions? |
DataStoreEngineDSOptions<TData extends object> | undefined |
Specifies the necessary options for storing data - |
Signature:
class Mixins<
TMixinMap extends Record<string, (arg: any, ctx?: any) => any>,
TMixinKey extends Extract<keyof TMixinMap, string> = Extract<keyof TMixinMap, string>,
>;Usage:
const mixins = new Mixins<TMixinMap>(config?: Partial<MixinsConstructorConfig>);A class for creating mixin functions that allow multiple sources to modify a target value in a highly flexible way.
Mixins are identified via their string key and can be added with add().
When calling resolve(), all registered mixin functions with the same key will be applied to the input value in the order of their priority.
If a mixin function has its stopPropagation flag set, no further mixin functions will be applied after it.
The TMixinMap template generic defines the mixin functions. Keys are the mixin names and values are functions that take the value as the first argument and an optional context object as the second, and return the modified value.
Important: the first argument and return type need to be the same. Also, if a context object is defined, it must be passed as the third argument in resolve().
Example - click to view
import { Mixins } from "@sv443-network/userutils";
const myMixins = new Mixins<{
myValue: (val: number, ctx: { factor: number }) => Promise<number>;
}>({
autoIncrementPriority: true,
});
myMixins.add("myValue", (val, { factor }) => val * factor);
myMixins.add("myValue", (val) => Promise.resolve(val + 1));
myMixins.add("myValue", (val) => val * 2, 1);
const result = await myMixins.resolve("myValue", 10, { factor: 0.75 });
// order of operations:
// 1. 10 * 2 = 20 (priority 1)
// 2. 20 * 0.75 = 15 (priority 0, index 0)
// 3. 15 + 1 = 16 (priority 0, index 1)
// result = 16Signature:
public add<TKey extends TMixinKey, TArg, TCtx>(
mixinKey: TKey,
mixinFn: (arg: TArg, ...ctx: TCtx extends undefined ? [void] : [TCtx]) => ReturnType<TMixinMap[TKey]>,
config?: Partial<MixinConfig> | number
): () => void;Registers a mixin function for the given key.
If a number is passed as config, it will be treated as the priority.
Returns a cleanup function that removes this mixin when called.
Mixins with the highest priority will be applied first. If two or more mixins share the exact same priority, they will be executed in order of registration (first come, first serve).
Signature:
public resolve<TKey extends TMixinKey, TArg, TCtx>(
mixinKey: TKey,
inputValue: TArg,
...inputCtx: TCtx extends undefined ? [void] : [TCtx]
): ReturnType<TMixinMap[TKey]>;Applies all mixins with the given key to the input value, respecting the priority and stopPropagation settings.
If some of the mixins are async, the method will also return a Promise.
Signature:
public list(): ({ key: string } & MixinConfig)[];Returns an array of objects that contain the mixin keys and their configuration objects, but not the mixin functions themselves.
Configuration object for the Mixins class.
| Property | Type | Description |
|---|---|---|
autoIncrementPriority |
boolean |
If true, an auto-incrementing integer priority will be used when none is specified (unique per mixin key). Defaults to false. |
defaultPriority |
number |
The default priority for mixins that do not specify one. Defaults to 0. |
defaultStopPropagation |
boolean |
The default stopPropagation value. Defaults to false. |
defaultSignal? |
AbortSignal | undefined |
The default AbortSignal for mixins that do not specify one. |
Configuration object for an individual mixin function.
| Property | Type | Description |
|---|---|---|
priority |
number |
The higher, the earlier the mixin will be applied. Supports floating-point and negative numbers. Defaults to 0. |
stopPropagation |
boolean |
If true, no further mixins will be applied after this one. |
signal? |
AbortSignal | undefined |
If set, the mixin will only be applied if the given signal is not aborted. |
An object containing the current version of the library and its re-exported dependency CoreUtils.
These versions are semver-compliant, without any prefix like v or range specifiers like ^, but might still contain suffixes like -beta.1 for pre-release versions.
⚠️ If you want to install both libraries at the same time, make sure to use this object to check that your installed version of CoreUtils matches the one that UserUtils is re-exporting, to avoid potential compatibility issues like broken class inheritance or feature mismatches. For most use cases, it should suffice to just use the re-exported CoreUtils features.
{
UserUtils: string; // semver-compliant version of this library
CoreUtils: string; // semver-compliant version of the re-exported CoreUtils library
}UserUtils' translation system is simpler than other industry standard libraries, but still powerful and flexible. It features support for nested keys, pattern-based transformation functions (with some predefined ones included), and various utility functions for runtime translation management, as well as full TypeScript type safety and autocomplete for translation keys.
The main translation functions are tr.for() and tr.use(), but there are also utility functions like tr.hasKey(), tr.addTranslations(), tr.getTranslations(), and more.
For fallbacks, tr.setFallbackLanguage() can be used to default to a specific language when a translation key is not found for the requested language.
Use the type TrKeys for creating a TS union type out of the keys of a translation object, which also works with nested keys, to provide better autocomplete and type safety when using tr.for() and tr.use().
The type TrObject defines the shape of the translation objects that are registered with tr.addTranslations().
Signature:
function tr.for<TTrKey extends string = string>(
language: string,
key: TTrKey,
...args: (Stringifiable | Record<string, Stringifiable>)[]
): string;Returns the translated text for the specified key in the specified language.
If the key is not found in the specified previously registered translation, the key itself is returned.
⚠️ Remember to register a language withtr.addTranslations()before using this function, otherwise it will always return the key itself.- By default, translation strings are returned as they are, but using the function
tr.addTransform()you can add functions that modify the translation string in various ways.
UserUtils comes with some predefined transforms out of the box, but custom ones are also easy to create for any other use case. Refer to the typeTransformTuplefor details.
Example - click to view
import { tr, type TrObject, type TrKeys } from "@sv443-network/userutils";
// create translation object:
const transEn = {
hello: "Hello, World!",
nested: {
key: "This is a nested key",
},
foo: "Foo: %1",
} as const satisfies TrObject;
// ^ `as const satisfies` ensures that the literal structural type is the one used by TS, while also ensuring it still matches `TrObject`
// create union type of all translation keys, including nested ones:
type KeysEn = TrKeys<typeof transEn>; // "hello" | "nested.key" | "foo"
// ^ it's recommended to create a type out of the keys of the most complete translation object (usually the same as the fallback language) and use that type for all calls to `tr.for()` and `tr.use()`, even for other languages that might have fewer keys.
// add translations object for "en" language:
tr.addTranslations("en", transEn);
// register the %n positional argument transform:
tr.addTransform(tr.transforms.percent);
// use tr.for() with autocomplete for the keys:
tr.for<KeysEn>("en", "hello"); // "Hello, World!"
tr.for<KeysEn>("en", "nested.key"); // "This is a nested key"
tr.for<KeysEn>("en", "foo", "bar"); // "Foo: bar" (using the predefined positional argument transform)
// using fallback language:
tr.for<KeysEn>("de", "hello"); // "hello" (key not found, returns key itself)
tr.setFallbackLanguage("en");
tr.for<KeysEn>("de", "hello"); // "Hello, World!" (fallback to English)Signature:
function tr.use<TTrKey extends string = string>(
language: string
): (key: TTrKey, ...args: (Stringifiable | Record<string, Stringifiable>)[]) => string;Creates a translation function for the specified language, allowing you to translate multiple strings without repeating the language parameter.
The returned function works exactly like tr.for(), minus the language parameter.
⚠️ Remember to register a language withtr.addTranslations()before using this function, otherwise it will always return the key itself.- By default, translation strings are returned as they are, but using the function
tr.addTransform()you can add functions that modify the translation string in various ways.
UserUtils comes with some predefined transforms out of the box, but custom ones are also easy to create for any other use case. Refer to the typeTransformTuplefor details.
Example - click to view
import { tr, type TrObject, type TrKeys } from "@sv443-network/userutils";
// create translation object:
const transEn = {
hello: "Hello, World!",
nested: {
key: "This is a nested key",
},
foo: "Foo: %1",
} as const satisfies TrObject;
// ^ `as const satisfies` ensures that the literal structural type is the one used by TS, while also ensuring it still matches `TrObject`
// create union type of all translation keys, including nested ones:
type KeysEn = TrKeys<typeof transEn>; // "hello" | "nested.key" | "foo"
// ^ it's recommended to create a type out of the keys of the most complete translation object (usually the same as the fallback language) and use that type for all calls to `tr.for()` and `tr.use()`, even for other languages that might have fewer keys.
// add translations object for "en" language:
tr.addTranslations("en", transEn);
// register the %n positional argument transform:
tr.addTransform(tr.transforms.percent);
// create a translation function for "en" language with autocomplete for the keys:
const t = tr.use<KeysEn>("en");
t("hello"); // "Hello, World!"
t("nested.key"); // "This is a nested key"
t("foo", "bar"); // "Foo: bar" (using the predefined positional argument transform)Signature:
function tr.hasKey<TTrKey extends string = string>(
language?: string,
key: TTrKey
): boolean;Checks if a translation key exists in the specified language or the set fallback language.
Returns false if the given language was not registered with tr.addTranslations().
Example - click to view
import { tr } from "@sv443-network/userutils";
tr.addTranslations("en", { hello: "Hello, World!" });
tr.hasKey("en", "hello"); // true
tr.hasKey("en", "goodbye"); // falseSignature:
function tr.addTranslations(language: string, translations: TrObject): void;Registers a new language and its translations. If the language already exists, it will be overwritten.
The translations can be a flat key-value object or infinitely nested objects, resulting in a dot-separated key.
Refer to the type TrObject for more details on the expected shape of the translations object.
In general, the value can either be a string, or another object that follows the same rules, allowing for infinite nesting.
When using nested objects, the keys will be concatenated with dots to form the final translation key. For example, the translation object { nested: { key: "foo" } } will create a translation key of nested.key with the value "foo".
Example - click to view
import { tr, type TrObject, type TrKeys } from "@sv443-network/userutils";
// create translation object:
const transEn = {
hello: "Hello, World!",
nested: {
key: "This is a nested key",
},
foo: "Foo: %1",
} as const satisfies TrObject;
// ^ `as const satisfies` ensures that the literal structural type is the one used by TS, while also ensuring it still matches `TrObject`
// create union type of all translation keys, including nested ones:
type KeysEn = TrKeys<typeof transEn>; // "hello" | "nested.key" | "foo"
// ^ it's recommended to create a type out of the keys of the most complete translation object (usually the same as the fallback language) and use that type for all calls to `tr.for()` and `tr.use()`, even for other languages that might have fewer keys.
// add translations object for "en" language:
tr.addTranslations("en", transEn);
// register the %n positional argument transform:
tr.addTransform(tr.transforms.percent);
// create a translation function for "en" language with autocomplete for the keys:
const t = tr.use<KeysEn>("en");
t("hello"); // "Hello, World!"
t("nested.key"); // "This is a nested key"
t("foo", "bar"); // "Foo: bar" (using the predefined positional argument transform)Signature:
function tr.getTranslations(language?: string): TrObject | undefined;Returns the translation object for the specified language, or undefined if the language is not registered.
If no language is provided, defaults to the fallback language.
Example - click to view
import { tr } from "@sv443-network/userutils";
tr.addTranslations("en", { hello: "Hello, World!" });
tr.getTranslations("en"); // { hello: "Hello, World!" }
tr.getTranslations("de"); // undefinedSignature:
getAllTranslations(asCopy = true): Record<string, TrObject>;Returns an object containing all registered translations, where keys are the language codes and values are the translation objects.
If asCopy is set to true (default), the returned object and all nested translation objects will be cloned using JSON.parse(JSON.stringify()) to prevent external mutation. If set to false, the actual internal translation objects will be returned, so any changes to them will affect the translations used by the library and can be used as an alternative to tr.addTranslations() for modifying translations.
Example - click to view
import { tr } from "@sv443-network/userutils";
tr.addTranslations("en", { hello: "Hello, World!" });
tr.addTranslations("de", { hello: "Hallo, Welt!" });
tr.getAllTranslations(); // { en: { hello: "Hello, World!" }, de: { hello: "Hallo, Welt!" } }
const translationsMutable = tr.getAllTranslations(false);
translationsMutable.en.hello = "Hi, World!";
tr.for("en", "hello"); // "Hi, World!"Signature:
function tr.deleteTranslations(language: string): boolean;Deletes the translations for the specified language from memory.
Returns true if the translations were found and deleted, false otherwise.
Example - click to view
import { tr } from "@sv443-network/userutils";
tr.addTranslations("en", { hello: "Hello, World!" });
tr.deleteTranslations("en"); // true
tr.deleteTranslations("de"); // falseSignature:
function tr.setFallbackLanguage(fallbackLanguage?: string): void;Sets the fallback language to use when a translation key is not found in the given language.
Pass undefined to disable fallbacks and just return the translation key if translations are not found.
Example - click to view
import { tr } from "@sv443-network/userutils";
tr.addTranslations("en", { hello: "Hello!", goodbye: "Goodbye!" });
tr.addTranslations("de", { hello: "Hallo!" });
tr.setFallbackLanguage("en");
const t = tr.use("de");
t("hello"); // "Hallo!"
t("goodbye"); // "Goodbye!" (falls back to "en")Signature:
function tr.getFallbackLanguage(): string | undefined;Returns the currently set fallback language, or undefined if no fallback language was set.
Example - click to view
import { tr } from "@sv443-network/userutils";
tr.getFallbackLanguage(); // undefined
tr.setFallbackLanguage("en");
tr.getFallbackLanguage(); // "en"Signature:
function tr.addTransform<TTrKey extends string = string>(
transform: TransformTuple<TTrKey>
): void;Adds a transform function to the translation system.
Use this to enable dynamic values in translations, for example to insert custom values or to denote a section that could be encapsulated by rich text.
The transform argument is a tuple of [pattern: RegExp, callback: TransformFn]. See the type TransformTuple for more details.
- Transforms are applied after resolving the initial translation string for any language, in the order they were added.
- Only when the given RegExp pattern was found inside a translation value, the corresponding TransformFn callback will be executed. As long as that function then correctly modifies and returns the
currentValueproperty (which all the default ones do), it won't matter if transforms are mixed and matched across different translation values. Just make sure that you only use one type of interpolation pattern per translation value to avoid potential conflicts between positional and keyed arguments. ⚠️ If a transform function throws an error, it will propagate up through the translation functions (tr.for(),tr.use(), etc.), so make sure to either handle errors within the transform function itself or wrap translation calls in try/catch blocks.
The TransformFn receives a single parameter which is an object with the following properties:
| Property | Type | Description |
|---|---|---|
currentValue |
string |
Current value, possibly in-between transformations. Should be modified and returned by the transform function. |
matches |
RegExpExecArray[] |
All matches as returned by RegExp.exec() |
language |
string |
The current or fallback language code. |
trKey |
TTrKey |
The translation key for which the transform is currently being applied. |
trValue |
string |
Translation value before any transformations. Use with caution to avoid conflicts with other transforms. |
trArgs |
(Stringifiable | Record<string, Stringifiable>)[] |
Array of all arguments passed to the translation function. |
Example - click to view
import { tr } from "@sv443-network/userutils";
// >> using predefined transforms:
// (scroll down for custom transform example)
tr.addTranslations("en", {
// uses the templateLiteral transform:
greeting: "Hello, ${name}!",
// uses the i18n transform:
notifications: "You have {{notifs}} notifications.",
// uses the percent transform:
status: "Status: %1",
});
// add multiple predefined transforms:
tr.addTransform(tr.transforms.templateLiteral);
tr.addTransform(tr.transforms.i18n);
tr.addTransform(tr.transforms.percent);
const t = tr.use("en");
// the transforms that are both positional and keyed can be used via object or positional arguments:
// templateLiteral transform:
t("greeting", { name: "John" }); // "Hello, John!"
t("greeting", "John"); // "Hello, John!"
// i18n transform:
t("notifications", { notifs: 42 }); // "You have 42 notifications."
t("notifications", 42); // "You have 42 notifications."
// transforms that only support positional arguments (like the percent transform) will try to stringify all arguments:
// percent transform:
t("status", "Online"); // "Status: Online"
t("status", { status: "Online" }); // "Status: [object Object]"
t("status", { toString: () => "Online" }); // "Status: Online"
// >> custom transform example:
// creating a custom transform that resolves '<c #hex>text</c>' to '<span style="color: #hex;">text</span>':
tr.addTransform([
// use g and m flags to match and replace all occurrences:
/<c\s+#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\s?>(.*?)<\/c>/gm,
// grab values from the regex groups and return the transformed string:
({ matches }) => `<span style="color: #${matches[1]}};">${matches[2]}</span>`,
]);
// add a new translation key while not overwriting the existing ones:
tr.addTranslations("en", {
...tr.getTranslations("en"),
colored: "<c #f00>This is red</c> and <c #0000ff>this is blue</c>.",
});
t("colored"); // "<span style="color: #f00;">This is red</span> and <span style="color: #0000ff;">this is blue</span>."Signature:
function tr.deleteTransform(patternOrFn: RegExp | TransformFn): boolean;Removes a transform function from the list of registered transform functions.
Returns true if the transform was found and deleted, false otherwise.
Example - click to view
import { tr, type TransformTuple } from "@sv443-network/userutils";
const myTransform: TransformTuple = [
/\$\{([a-zA-Z0-9$_-]+)\}/gm,
({ matches }) => matches[1] ?? "",
];
tr.deleteTransform(myTransform[0]); // false
tr.addTransform(myTransform);
tr.deleteTransform(myTransform[0]); // true
tr.deleteTransform(myTransform[0]); // falsePredefined transform functions for quickly adding custom argument interpolation.
Enable them by passing them to tr.addTransform().
Currently available transforms:
| Key | Pattern | Type(s) |
|---|---|---|
templateLiteral |
${key} |
Keyed / Positional |
i18n |
{{key}} |
Keyed / Positional |
percent |
%n |
Positional |
Example - click to view
import { tr } from "@sv443-network/userutils";
tr.addTranslations("en", {
greeting: "Hello, ${name}! You have ${notifs} notifications.",
message: "Hello, %1! You have %2 notifications.",
});
tr.addTransform(tr.transforms.templateLiteral);
tr.addTransform(tr.transforms.percent);
const t = tr.use("en");
// templateLiteral supports both keyed and positional:
t("greeting", { name: "John", notifs: 42 }); // "Hello, John! You have 42 notifications."
t("greeting", "John", 42); // "Hello, John! You have 42 notifications."
// percent is positional only:
t("message", "John", 42); // "Hello, John! You have 42 notifications."Signature:
type TrKeys<TTrObj, P extends string = "">;Generic type that extracts all keys from a flat or recursive translation object into a union type.
Nested keys will be joined with a dot (.).
Example - click to view
import { tr, type TrKeys } from "@sv443-network/userutils";
const trEn = {
hello: "Hello, World!",
nested: {
key: "This is a nested key",
},
} as const;
tr.addTranslations("en", trEn);
type MyKeys = TrKeys<typeof trEn>; // "hello" | "nested.key"
const t = tr.use<MyKeys>("en");interface TrObject {
[key: string]: string | TrObject;
}Translation object to pass to tr.addTranslations().
Can be a flat object of identifier keys and translation text values, or an infinitely nestable object containing the same.
type TransformFn<TTrKey extends string = string> = (props: TransformFnProps<TTrKey>) => Stringifiable;Function that transforms a matched translation string into another string.
It is passed as the second item in the type TransformTuple to the function tr.addTransform().
When the function gets called, it receives a single object parameter. Refer to the function tr.addTransform() documentation for the properties of this object and other important notes on using transform functions.
type TransformTuple<TTrKey extends string = string> = [RegExp, TransformFn<TTrKey>];Translation transform pattern and function in tuple form, passed to tr.addTransform().
Used when creating custom transforms that are not included in the predefined transforms.
The first item in the tuple is a RegExp pattern that is used to find matches in translation values.
Use groups (...) to capture values for later use in the TransformFn via the matches property. (You can also prevent capturing with (?:...) if you just need the parens for the pattern but don't need the values.)
Make sure to use the g and m flags so that the pattern can match multiple occurrences in a single translation value, including translation values that contain \n line breaks.
You can use a website like regex101.com to test and debug your RegExp patterns. Just make sure the flavor is set to ECMAScript (JavaScript) and to include the appropriate flags.
The second item is the TransformFn that transforms the matched translation string into another string.
It is a function that takes a single object parameter. Refer to the function tr.addTransform() documentation for the properties of this object and other important notes on using transform functions.
Example - click to view
import { tr, type TransformTuple } from "@sv443-network/userutils";
// simple transform that turns '[icon:name]' into '<i class="icon" data-icon="name"></i>':
const iconTransform: TransformTuple = [
/\[icon:([a-zA-Z0-9_-]+)\]/gm,
({ matches }) => `<i class="icon" data-icon="${matches[1]}"></i>`,
];
// add the custom transform and the predefined templateLiteral transform:
tr.addTransform(iconTransform);
tr.addTransform(tr.transforms.templateLiteral);
tr.addTranslations("en", {
warning: "[icon:warning] Warning: ${message}",
});
const t = tr.use("en");
console.log(t("warning", { message: "This is a warning message!" }));
// Output: "<i class="icon" data-icon="warning"></i> Warning: This is a warning message!"Signature:
class PlatformError extends DatedError;Usage:
throw new PlatformError(message: string, options?: ErrorOptions);Thrown when the current platform doesn't support a certain feature, like calling a DOM function in a non-DOM environment.
Extends from DatedError, which has a date property that contains the date and time when the error was created.
Example - click to view
import { PlatformError } from "@sv443-network/userutils";
if(typeof document === "undefined")
throw new PlatformError("This feature requires a DOM environment");Made with ❤️ by Sv443
If you like this library, please consider supporting development