utilities-ts
v0.1.0
Published
TypeScript utility functions for general-purpose and DOM manipulation, with full tree-shaking support.
Maintainers
Readme
utilities-ts
TypeScript utility functions for general-purpose use and DOM manipulation. Fully tree-shakeable, ESM-only, zero dependencies.
Background
Originally I had these functions in a devtools snippet and all function names started with a 'z' for easier auto completion access. I have not changed this pattern, but now you know why ;)
Install
npm install utilities-ts
# or
yarn add utilities-tsRuntime support: Node 18+ for the utilities-ts/general entrypoint.
If you are working on this repository, use Corepack so the pinned Yarn version from package.json is used automatically:
corepack enable
yarn installThis repository currently pins [email protected] via the packageManager field.
Running yarn install or npm install in a fresh clone now also builds dist/ and the DevTools snippets automatically via the install lifecycle scripts.
Import paths
| Path | Contents |
| ---------------------- | --------------------------------------------------------- |
| utilities-ts | Everything (general + DOM) |
| utilities-ts/general | Non-DOM utilities only (safe in Node.js / SSR) |
| utilities-ts/dom | DOM utilities + re-exports of commonly used general utils |
import { zstringify, ztryIt } from 'utilities-ts/general';
import { zgetAllElements, zclearALlTimeoutsAndIntervals, zstringifyDOM } from 'utilities-ts/dom';DevTools snippets
Run npm run build:snippets or yarn build:snippets to generate plain browser-script bundles in snippets.
Generated files:
These files are meant for DevTools paste/snippet usage, not ESM imports:
- They contain no
importorexportsyntax. - Pasting
index.iife.jsexposes the full library asglobalThis.zutils. - The same paste also copies every exported utility onto
globalThis, so functions likezgetElement(...)are callable immediately. general.iife.jsanddom.iife.jsdo the same underglobalThis.zutilsGeneralandglobalThis.zutilsDom.- They are repository artifacts for GitHub / DevTools use and are not included in the npm package.
Typical workflow:
- Run
npm run build:snippetsoryarn build:snippets. - Open one of the files in snippets.
- Paste it into a DevTools console or save it as a DevTools Snippet.
Highlights
DOM — Element querying
zgetElement, zgetElements, and zgetOneElement are wrappers around querySelector/querySelectorAll. They accept a selector string or an array of selectors (joined as a comma-separated list), and automatically prefix selectors with :scope when a non-document root is provided.
import { zgetElement, zgetElements, zgetOneElement } from 'utilities-ts/dom';
zgetElement('button.submit', form); // first match inside form, or null
zgetElements('[data-id]'); // all matches → Element[]
zgetOneElement('h1', container); // null + warning if not exactly one matchzgetElementDeep, zgetAllElements, and zgetOneElementDeep pierce shadow roots and same-origin iframes, making them suitable for inspecting pages with web components or embedded frames.
import { zgetElementDeep, zgetAllElements, zgetOneElementDeep } from 'utilities-ts/dom';
zgetElementDeep('video'); // first <video> anywhere in the full DOM tree
zgetAllElements('input'); // all <input>s including inside shadow roots/iframes
zgetOneElementDeep('[data-root]'); // null + warning if not exactly one matchDOM — Parent / ancestor traversal
import { zgetParentElement, zclosest, ztraverseParentElementsUntilCondition } from 'utilities-ts/dom';
// Like el.parentElement but resolves through shadow root hosts and iframe boundaries
zgetParentElement(el);
// Like el.closest() but resolves through shadow root host boundaries
zclosest(el, '.card');
// Walk up the tree until a condition is met; returns { el, traverseCount }
const { el } = ztraverseParentElementsUntilCondition(startEl, (parentEl) => parentEl.matches('.container'));DOM — Visibility
import { zelIsVisible, zelIsActuallyVisible } from 'utilities-ts/dom';
// True if the element has non-zero bounds, non-zero opacity, and is not visibility:hidden
zelIsVisible(el);
// Like zelIsVisible but also walks the parent chain to catch display:none or opacity:0 ancestors
zelIsActuallyVisible(el);
// Pass inFlow: true to treat visibility:hidden as visible (element still occupies space)
zelIsVisible(el, true);DOM — Styling
import { zstyleElement, zinsertCss } from 'utilities-ts/dom';
// Apply inline styles via setProperty — accepts a CSS string or a plain object
zstyleElement(el, 'color: red !important; font-size: 14px');
zstyleElement(el, { color: 'red', 'font-size': '14px' });
// Inject a <style> element into the document (appended to document.head by default)
zinsertCss('body { background: #1a1a1a; color: white; }');
zinsertCss('.card { border-radius: 8px; }', { id: 'my-styles', rootElement: shadowRoot });DOM — Style diffing
zgetBeforeAfterStyleDiffs snapshots the computed styles of an element (and optionally all its children) before and after a timeout delay, returning only the properties that changed.
import { zgetBeforeAfterStyleDiffs } from 'utilities-ts/dom';
// Trigger an animation or interaction, then inspect what changed
const diffs = await zgetBeforeAfterStyleDiffs(el, { timeout: 500 });
// → [{ el, changed: { 'opacity': { before: '1', after: '0' } } }, ...]
// Narrow to specific properties, or exclude noisy ones
const diffs = await zgetBeforeAfterStyleDiffs(el, {
include: ['opacity', 'transform'],
includeChildren: false,
});DOM — Sorting and common ancestors
import { zsortElementsByTextContent, zgetCommonAncestorContainers } from 'utilities-ts/dom';
// Re-orders elements in the DOM by their text content (locale-aware)
// If rootEl is provided, all elements are appended there; otherwise each is moved within its own parent
zsortElementsByTextContent(elements);
zsortElementsByTextContent(elements, containerEl);
// Given a set of elements, find the minimal set of ancestor containers that together cover all of them.
// Also returns a single containerContainsAll (the common ancestor of everything).
const result = zgetCommonAncestorContainers(elements);
result?.leastNumberOfContainersForElements; // [{ container, contains }]
result?.containerContainsAll; // { commonAncestor, traverseCount, contains }DOM — Utilities
import { zclearALlTimeoutsAndIntervals, zgetWindowNoneDefaults, zstringifyDOM } from 'utilities-ts/dom';
// Clear all active setTimeout and setInterval timers
// Pass `true` to also clear timers in all same-origin iframes
zclearALlTimeoutsAndIntervals();
zclearALlTimeoutsAndIntervals(true);
// Returns window properties not present in a default empty iframe window —
// useful for inspecting what scripts or extensions have added to the global scope
const globals = zgetWindowNoneDefaults();
const globals = zgetWindowNoneDefaults(['myKnownGlobal']);
// Like zstringify but renders DOM elements as CSS selector strings (tagName#id.class)
zstringifyDOM({ el: document.querySelector('button#submit') }); // '{el: button#submit}'
zstringifyDOM(document.body, { searchString: 'submit', returnSearchInfo: true });DOM — HTML formatting
import { zreplaceAmpersandCharacterCodes, zformatOuterHTMLString, zformatElementOuterHTML } from 'utilities-ts/dom';
zreplaceAmpersandCharacterCodes('Tom & Jerry'); // 'Tom & Jerry'
zformatOuterHTMLString('<div><span>Hello</span></div>', { collapseText: true });
// → formatted multi-line HTML string
zformatElementOuterHTML(document.body, { collapseText: true });General — Array utilities
Handles deep keys by dot-separated key path (or array of key segments)
import {
zmapKey,
zmapKeys,
zmapFunction,
zfilterKey,
zfilterFunction,
zgroupBy,
zgroupByUD,
} from 'utilities-ts/general';
// Map a dot-separated key path over an array of objects
zmapKey(users, 'address.city'); // ['London', 'Berlin']
zmapKey(users, 'id'); // [1, 2, 3]
// Map multiple key paths at once, merged into one object per element
zmapKeys(users, ['id', 'address.city']);
// → [{ id: 1, city: 'London' }, { id: 2, city: 'Berlin' }]
zmapKeys(users, ['id', 'address.city'], { keepPath: true });
// → [{ id: 1, 'address.city': 'London' }, ...]
// Map a sync or async function over an array
zmapFunction(items, (x) => x * 2); // returns R[]
zmapFunction(items, async (x) => fetch(x)); // returns Promise<R[]>
// Filter by a dot-separated key path (keeps elements where the value is truthy)
zfilterKey(users, 'address.city'); // users that have a city
// Filter by a sync or async predicate
zfilterFunction(items, (x) => x > 0);
zfilterFunction(items, async (x) => isAvailable(x)); // returns Promise<T[]>
// Group by a key path
zgroupBy(users, 'department');
// → { Engineering: [...], Design: [...] }
// Group and split into uniques (groups of 1) and duplicates (groups > 1)
const { all, uniques, duplicates } = zgroupByUD(users, 'email');General — String helpers
import { zreplaceString, zremoveString, zfixMojibake, ztextIncludes } from 'utilities-ts/general';
zreplaceString('a-b-c', '_', '-'); // 'a_b_c'
zremoveString('1,234,567', ','); // '1234567'
zfixMojibake('café'); // 'café'
ztextIncludes('Hello world', 'hello'); // true
ztextIncludes('Price incl. VAT', 'price', { fromStart: true }); // trueGeneral — Async utilities
import { zwait, ztryIt, zpromiseFunc, ztimeoutFunction } from 'utilities-ts/general';
// Delay execution
await zwait(500);
// Go-style error handling — no try/catch needed
// Accepts a sync function, async function, or a Promise
const [data, err] = await ztryIt(fetchData);
if (err) console.error(err.message);
// Wrap a callback function that resolves via callback(result) in a Promise
const preferences = await zpromiseFunc(InspectorFrontendHost.getPreferences);
// Call a function after a delay and return its result as a Promise
const result = await ztimeoutFunction(myFunc, 1000);General — Stringify
zstringify converts any JavaScript value to a readable string. It handles Map, Set, Date, RegExp, Error, circular references, DOM elements, and deeply nested structures.
import { zstringify } from 'utilities-ts/general';
zstringify({ a: 1, b: [2, 3] }); // '{a: 1, b: [2, 3]}'
zstringify(new Map([['x', 1]])); // 'Map{x => 1}'
zstringify(new Date('2026-01-01')); // 'Thu Jan 01 2026 ...'
zstringify(new Set([1, 2])); // 'Set{1, 2}'
// Search for a value and return which paths matched
const { stringified, matchingPaths } = zstringify(data, {
searchString: 'hello',
returnSearchInfo: true,
});General — BFS path search
Useful for searching large nested objects, things on window etc
import { zfindMatchingPaths, zfindMatchingPathsShortestByFunction } from 'utilities-ts/general';
// Find the shortest path(s) to matching terminal values (BFS — shortest first)
zfindMatchingPaths(obj, { searchString: 'hello' });
// → ['x', 'y.z', 'a.b.c']
// Optionally include matching object property names too
zfindMatchingPaths(obj, { searchString: 'title', includeKeys: true });
// → ['article.title']
// Return only the matched values
zfindMatchingPaths(obj, { searchString: 'hello', returnType: 'values' });
// → ['hello', 'helloooo!', 'hello']
// Return entries instead of only paths
zfindMatchingPaths(obj, { searchString: 'hello', returnType: 'entries' });
// → [{ path: 'x', value: 'hello' }, { path: 'y.z', value: 'helloooo!' }, { path: 'a.b.c', value: 'hello' }]
// Find paths using any predicate — checked before descending so objects/arrays can match too
zfindMatchingPathsShortestByFunction(obj, {
isMatch: (v) => Array.isArray(v) && v.length > 0,
});
// → [{ path: 'items', value: [...] }]
// Custom equality for deduplication
zfindMatchingPathsShortestByFunction(obj, {
isMatch: (v) => typeof v === 'string',
isEqual: (a, b) => (a as string).toLowerCase() === (b as string).toLowerCase(),
});License
MIT
