belter
Small browser helpers for code that embeds things in pages.
npm install belter
import { iframe, awaitFrameWindow, onResize, watchElementForClose, cleanup } from 'belter';
const clean = cleanup();
const frame = iframe({ url: 'https://example.com/widget', container: '#widget' });
clean.register(() => frame.remove());
const win = await awaitFrameWindow(frame); // the frame's window, once loaded
const resize = onResize({ element: frame, handler: ({ width, height }) => { ... } });
const watch = watchElementForClose({ element: frame, handler: () => clean.all() });
ES modules, TypeScript types included, ES2019 syntax. For current browsers: nothing here exists to work around an old one. Depends on cross-domain-utils and zalgo-promise; the functions that wait for something return a ZalgoPromise, which settles synchronously when it can and is also a plain thenable.
What's here
Frames and popups
| Function | Gives |
|---|---|
iframe({ url?, attributes?, style?, classes?, container? }) |
A transparent, borderless iframe with an id, appended and then pointed at url |
popup(url, { name?, width?, height?, closeOnUnload?, ...features }) |
A popup centred on this window; throws PopupOpenError when blocked |
awaitFrameLoad(frame), awaitFrameWindow(frame) |
Resolve once the frame has loaded, with the frame or its window |
writeElementToWindow(win, htmlElement) |
Replace a same-origin window's document with the children of an <html> element |
Elements
| Function | Gives |
|---|---|
createElement({ tag?, id?, classes?, style?, attributes?, html?, container?, doc? }) |
An element with everything set |
getElement(ref, doc?), getElementSafe(ref, doc?) |
An element from a selector or the element itself; the safe one returns undefined |
elementReady(ref, doc?) |
Resolves with the element as soon as it exists, rejects once the document has loaded without it |
isElement(value), isElementVisible(el), isElementClosed(el) |
Checks: duck-typed element, takes up space, removed from its document |
showElement(el), hideElement(el), destroyElement(el) |
Display toggles (display: none !important) and removal |
addEventListener(target, event, handler) |
A listener that returns { cancel } |
describeElementRef(ref) |
How a reference reads in an error message |
Watching
| Function | Gives |
|---|---|
watchElementForClose({ element, handler }) |
Calls handler once when the element leaves its document, even if it is put straight back (ancestor observers plus a one-second poll); returns { cancel } |
onResize({ element, handler, width?, height?, win? }) |
Calls handler({ width, height }) now and whenever the watched dimensions change while visible |
waitForDocumentReady(doc?), waitForDocumentBody(doc?), waitForWindowReady(win?) |
Resolve once the document is parsed, its body exists, or the window has loaded |
Shadow DOM
| Function | Gives |
|---|---|
isShadowElement(node), getShadowRoot(node), getShadowHost(node) |
Where a node sits relative to a shadow root |
insertShadowSlot(element) |
A light-DOM element that is projected into element, through nested shadow roots |
Script tags
| Function | Gives |
|---|---|
getScriptUID(script) |
A stable id for a script tag: its data-uid, else a hash of its src and data attributes |
getCurrentScript(), getCurrentScriptUID() |
The script tag that loaded this code (classic scripts only), and its uid, or a random one |
Functions and state
| Function | Gives |
|---|---|
memoize(fn) |
Caches by argument values (functions by identity, elements as {}); .reset() clears |
memoizePromise(fn) |
Shares one in-flight promise between identical calls, then forgets it |
weakMapMemoize(fn) |
Caches on one object argument, held weakly |
once(fn), debounce(fn, time?), safeInterval(fn, time) |
The usual; the interval never overlaps itself and returns { cancel } |
eventEmitter<Events>() |
on, once, trigger, triggerOnce, reset; triggers resolve once every handler has |
cleanup() |
register(task) and all(err?); a task registered after all runs at once |
getOrSet({ map, key, create }), tryCatch(fn) |
Odds and ends |
Values
| Function | Gives |
|---|---|
uniqueID() |
uid_ and sixteen hex characters |
base64encode(str), base64decode(str) |
Unicode-safe, no padding |
stringifyError(err) |
A string for any thrown value, never throws itself |
dotify(obj), undotify(obj) |
{ a: { b: 1 }, c: [ 1, 2 ] } to and from { 'a.b': '1', 'c[]': '1,2' } |
parseQuery(str), formatQuery(obj), extendQuery(str, obj), extendUrl(url, { query?, hash? }) |
Query strings with encodeURIComponent values |
isPerc(v), isPx(v), toNum(v), toPx(v), toCSS(v), percOf(num, perc), normalizeDimension(v, max) |
CSS dimensions: 100px, 50%, or a bare number |
noop, identity, isDefined, assertExists(value, what), isPlainObject |
Tiny |
Types: Cancelable, ElementRef, Dimensions, Query, PopupOptions, EventEmitter, Cleanup, Memoized, TryCatchResult.
Changes in 3.0
Published as belter again (2.x lived under @krakenjs). ES modules and TypeScript only, and the surface is what current browser code needs rather than everything 1.x and 2.x accumulated:
- Gone, because the platform has it or no supported browser needs it:
extend(Object.assign),values,arrayFrom,getGlobal(globalThis),appendChild,addClass/removeClass,isLocalStorageEnabled, thedocument.writepath (writeToWindow), vendor-prefixed animations (animate,setVendorCSS,showAndAnimate,animateAndHide), theallowTransparencyandabout:blankiframe quirks, the stack-trace script lookup, andExtendableError. - Gone, because it belonged to one product or one era:
device.js(everyisIE/isIos/isAndroid/ webview sniff),screenHeights.js,http.js,experiment.js,storage.js,decorators.js,test.js, the safe-weakmap global namespace,sanitizeUrl,regex/regexAll/regexMap/regexTokenize,patchMethod,awaitKey,cycle,copyProp,defineLazyProp,replaceObject,promisify,inlineMemoize,memoize.clear,dedupeErrors,hashStr,svgToBase64,get,perc/min/max/roundUp,getPerformance/getPageRenderTime/getResourceLoadTime,getBrowserLocales,hasMetaViewPort,onClick,getScript,redirect,urlWillRedirectPage,htmlEncode,querySelectorAll,fixScripts,preventClickFocus,camelToDasherize/dasherizeToCamel/capitalizeFirstLetter,isRegex,isInteger/isFloat,serializePrimitive/deserializePrimitive,unique,constHas,removeFromArray,objFilter,promiseDebounce,safeTimeout,weakMapMemoizePromise,getObjectID,getFunctionName/setFunctionName,bindEvents,setStyle,makeElementVisible/makeElementInvisible.submitFormlives incross-domain-utils. - Options objects where there were three or more positional arguments:
createElement({ ... }),iframe({ ... }),watchElementForClose({ element, handler }),onResize({ element, handler, ... }),getOrSet({ map, key, create }).assertExists(value, what)takes the value first.cleanup()takes no object and has noset. watchElementForCloseno longer plants a sacrificial iframe inside the element;onResizeusesResizeObserverand the window'sresizeevent with no polling;elementReadyuses aMutationObserverinstead of a 10ms poll;uniqueIDisuid_plus sixteen random hex characters;memoizekeys on argument values only (nothisnamespace, no expiry).isDocumentReady,waitForDocumentReady,waitForDocumentBody,elementReadyandcreateElementtake an optional document, so they work on a frame's document too.- The test suite runs in a real browser (Playwright) with real frames, popups and shadow roots, and in Node for the pure helpers.
License
Apache-2.0