262588213843476 · Gist

// sh.mts: javascript shorthand // #region array tools /** id function (returns first argument) */ export const id=<T,>(x: T): T=>x /** create an array filled with n copies of x */ export const af=<T,>(n: number, x: T): T[]=>Array(n).fill(x) // TODO: make this 'afl' or 'fil' (aa?) /** loop i from 0..n, calling f(i) */ export const ii=(n: number, f: (i: number)=>void)=>{for(let i=0;i<n;i++)f(i)} /** map f over [0..n] */ export const im=<T,>(n: number, f: (i: number)=>T): T[]=>af(n,0).map((_,i)=>f(i)) /** return the numbers [0..n] */ export const ia=(n: number): number[]=>im(n,id) /** extract the values of a for the given indices */ export const at=<T,>(a: T[], ixs: number[]): T[]=>ixs.map(i=>a[i]) /** index of each y in ys in xs */ export const io=<T,>(xs: readonly T[], ys: readonly T[]): number[]=>ys.map(y => xs.indexOf(y)) // Use readonly for safety and fix map fn /** Returns a[0] **/ export const a0=<T,>(a: T[]): T=>a[0] /** Returns a[1] **/ export const a1=<T,>(a: T[]): T=>a[1] /** Binary search: find lowest index i where xs[i] <= x, or -1 if none **/ export function bin(xs: number[], x: number): number { let a=0, z=xs.length-1, m, i if (!xs.length || x<xs[a]) return -1 if (x>=xs[z]) return z // Handle case where x is >= last element while (a<=z) { i = a + (m = (z-a)>>>1) if(!m || x === xs[i]) return i if(x < xs[i]) z=i; else a=i} console.warn("bin: failed to find index") // should never happen return a} // test suite for bin(xs,x) let actual = [1,3,5,7, 2,4,6, 0,8].map(x=>bin([1,3,5,7],x)) let expect = [0,1,2,3, 0,1,2, -1,3] for (let i=0;i<actual.length;i++) if(actual[i]!==expect[i]) console.warn(`${actual[i]}!==${expect[i]} at index ${i}`) /** Odometer: Generates a grid of coordinate pairs (x,y) for width x and height y. Ex: odom(2,3) => [[0,0],[0,1],[0,2],[1,0],[1,1],[1,2]] */ export const odom=(x: number, y: number): [number, number][]=>{ let r:[number, number][]=[]; for(let j=0;j<y;j++) for(let i=0;i<x;i++) r.push([i,j]); return r } // #endregion // #region misc js /** Shorthand for Math functions (min, max, sin, tan, cos) **/ export const {min,max,sin,tan,cos}=Math /** Shortcut for random number generation **/ export const ran=Math.random /** Shortcut for floor function **/ export const flo=Math.floor /** Shortcut for ceil function **/ export const cei=Math.ceil /** Shortcut for round function **/ export const rou=Math.round /** Shortcut to parse integer **/ export const int=parseInt /** Shortcut to parse float **/ export const num=parseFloat /** Sorts array with an optional comparator **/ export const srt=<T,>(a: T[], f?: (a: T, b: T) => number): T[]=>a.toSorted(f) /** Returns length property of an object or string **/ export const len=(x: {length: number} | string): number=>x.length /** Reference to the document object **/ export const doc=document /** Checks if a value is undefined or null **/ export const und=(x: any): x is undefined | null =>x===undefined || x === null /** Shorthand to convert array-like objects to arrays **/ export const arf=Array.from // TODO: make this 'af' /** Object.assign shortcut **/ export const oas=Object.assign /** Creates an object from entries **/ export const ofe=Object.fromEntries /** Returns object's keys **/ export const oks=Object.keys /** Bound console.log shortcut **/ export const log=console.log.bind(console) /** Request animation frame helper **/ export const raf=(cb: FrameRequestCallback): number =>window.requestAnimationFrame(cb) /** Create a string from a Unicode code point **/ export const chr=String.fromCodePoint /** Returns code point(s) for a string **/ export const ord=(s: string): number | (number | undefined)[] | undefined => s.length === 1 ? s.codePointAt(0) : arf(s).map(c => s.codePointAt(0)) /** Fetch text and apply callback **/ export const ftx=(u: string | URL | Request, cb: (text: string)=>void): Promise<void>=>fetch(u).then(r=>r.text()).then(cb) /** Fetch JSON with optional method and data **/ export const fjs=<T=any,>(u: string | URL | Request, m?: string, d?: any): Promise<T>=>{ let f = (und(m)||m==='GET') ? fetch(u) : fetch(u,{method:m,headers:{'Content-type':'application/json'}, body:jss(d)}) return f.then(r=>r.json())} /** Shortcut to JSON.parse **/ export const jsp=JSON.parse /** Shortcut to JSON.stringify **/ export const jss=(x: any, y?: (key: string, value: any) => any | (number | string)[] | null): string =>JSON.stringify(x,y) /** Shorthand setTimeout wrapper **/ export const sto=(ms: number, cb: ()=>void): ReturnType<typeof setTimeout>=>setTimeout(cb,ms) // deb(f,ms) returns a debounced function that delays invoking f until after ms milliseconds have elapsed since the last time it was invoked. /** Creates a debounced version of a function **/ export function deb<T extends (...args: any[]) => any>(f: T, ms: number): (...args: Parameters<T>) => void { let timer: ReturnType<typeof setTimeout> | null = null; return function(this: ThisParameterType<T>, ...args: Parameters<T>) { if (timer) clearTimeout(timer); timer = setTimeout(() => { f.apply(this, args); }, ms)}} // #endregion // #region dom tools /** Create an HTMLElement with attributes and children **/ export const ce=<K extends keyof HTMLElementTagNameMap,>(t: K, a?: Record<string, string>, es?: (Node | string)[]): HTMLElementTagNameMap[K]=>app(ats(doc.createElement(t),a), ...(es||[])) /** Create an Element in a specific namespace with attributes and children **/ export const cen=(n: string | null, t: string, a?: Record<string, string>, es?: (Node | string)[]): Element =>app(ats(doc.createElementNS(n,t),a), ...(es||[])) /** Create an SVG element with attributes and children **/ export const svg=<K extends keyof SVGElementTagNameMap,>(t: K, a?: Record<string, string>, es?: (Node | string)[]): SVGElementTagNameMap[K]=>(es||=[],cen(svg.ns,t,a,es) as SVGElementTagNameMap[K]) svg.ns='http://www.w3.org/2000/svg' /** Create a new Animation instance **/ export const ani=(a: AnimationEffect | null, tl?: AnimationTimeline | null): Animation =>new Animation(a, tl) /** Create a KeyframeEffect instance **/ export const kfe=(e: Element | null, ks: Keyframe[] | PropertyIndexedKeyframes | null, o?: number | KeyframeEffectOptions)=>new KeyframeEffect(e,ks,o) /** Shortcut to create an Animation from an element and keyframes **/ export const akf=(e: Element | null, ks: Keyframe[] | PropertyIndexedKeyframes | null, o?: number | KeyframeEffectOptions)=>ani(kfe(e,ks,o)) /** Create an SVG element representing a drawing surface **/ export const S=(a?: Record<string, string>, es?: (Node | string)[]): SVGSVGElement => svg('svg',oas({ width:640,height:480,viewBox:'-320 -240 640 480'},a),es) /** Create an SVG circle element **/ export const C=(a?: Record<string, string>): SVGCircleElement =>svg('circle',oas({cx:0,cy:0,r:0},a)) /** Create an SVG rectangle element **/ export const R=(a?: Record<string, string>): SVGRectElement =>svg('rect',oas({x:-5,y:-5,width:10,height:10},a)) /** Create an SVG group element **/ export const G=(a?: Record<string, string>, es?: (Node | string)[]): SVGGElement =>svg('g',a,es) // -- dom shorthand /** Returns attribute value if v is undefined, otherwise sets attribute **/ export function att(e: Element, a: string): string | null export function att<T extends Element,>(e: T, a: string, v: string | number | boolean): T export function att<T extends Element,>(e: T, a: string, v?: string | number | boolean): T | string | null {return und(v)?e.getAttribute(a):(e.setAttribute(a,String(v)),e)} /** Sets multiple attributes from a key-value object **/ export function ats<T extends Element,>(e: T, kv?: Record<string, string | number | boolean>): T { if (kv) for (let k in kv) e.setAttribute(k,String(kv[k])); return e} /** Gets or sets inline styles on an element **/ export function sty(e: HTMLElement, a: keyof CSSStyleDeclaration): string | null export function sty<T extends HTMLElement,>(e: T, a: keyof CSSStyleDeclaration, v: string | null): T export function sty<T extends HTMLElement,>(e: T, a: keyof CSSStyleDeclaration, v?: string | null): T | string | null {return und(v) ? e.style[a as any] : (e.style[a as any]=v,e)} /** Checks if an element has a specific attribute **/ export const hat=(e: Element, a: string): boolean =>e.hasAttribute(a) /** Removes an attribute from an element **/ export const rat=(e: Element, a: string): void =>e.removeAttribute(a) /** Appends nodes or strings to an element or array **/ export function app<T extends Node,>(x: T, ...ys: (Node | string)[]): T export function app<T,>(x: T[], ...ys: T[]): T[] export function app(x: any, ...ys: any[]): any {return (x.append||x.push).bind(x)(...ys),x} /** Gets or sets the innerHTML of an element **/ export function iht(e: Element): string export function iht<T extends Element,>(e: T, h: string): T export function iht<T extends Element,>(e: T, h?: string): T | string {return und(h)?e.innerHTML:(e.innerHTML=h,e)} /** Gets or sets the innerText of an element **/ export function itx(e: HTMLElement): string export function itx<T extends HTMLElement,>(e: T, t: string): T export function itx<T extends HTMLElement,>(e: T, t?: string): T | string {return und(t)?e.innerText:(e.innerText=t,e)} /** Adds a class to an element **/ export const cla=<T extends Element,>(c: string, e: T): T=>(e.classList.add(c),e) /** Removes a class from an element **/ export const clr=<T extends Element,>(c: string, e: T): T=>(e.classList.remove(c),e) /** Checks if an element has a given class **/ export const hac=(e: Element, c: string): boolean =>e.classList.contains(c) /** Returns the bounding rectangle of an element **/ export const gbc=(e: Element): DOMRect =>e.getBoundingClientRect() /** Defines a custom element with a tag name **/ export const cus=(t: string, c: CustomElementConstructor): void => { try { customElements.define(t, c) } catch (e) { if (e instanceof DOMException && e.name === 'NotSupportedError' && e.message.includes('has already been used')) { console.warn(`Custom element "${t}" already defined, may be a duplicate import`) } else { throw e } } } /** Gets an element by id from document or fragment **/ export function eid(x: string): HTMLElement | null export function eid(x: DocumentFragment, y: string): HTMLElement | null export function eid(x: string | DocumentFragment, y?: string): HTMLElement | null { let [base, id]: [Document | DocumentFragment, string] = y===undefined ? [doc,x as string] : [x as DocumentFragment, y as string] return base instanceof Document ? base.getElementById(id) : base.querySelector(`#${CSS.escape(id)}`) } /** Proxy interface for shorthand element lookup **/ export interface Ei { (x: string): HTMLElement | null; (x: DocumentFragment, y: string): HTMLElement | null; [key: string]: HTMLElement | null; } /** Type for the proxied eid function **/ type EidFn = { (x: string): HTMLElement | null; (x: DocumentFragment, y: string): HTMLElement | null; } /** Proxied eid to allow property shorthand access **/ export const ei = new Proxy(eid as EidFn, { get: (targetEid, prop) => targetEid(typeof prop === 'symbol' ? prop.toString() : String(prop)), apply: (targetEid, thisArg, args: [string] | [DocumentFragment, string]) => { if (args.length === 1) return targetEid(args[0]) else return targetEid(args[0], args[1])}}) as Ei /** shorthand: y ? x.querySelector(y) : document.querySelector(x) **/ export function qs<T extends Element = HTMLElement>(x: string | ParentNode, y?: string): T | null { const [base, sel] = und(y) ? [doc, x as string] : [x as ParentNode, y as string] return base.querySelector<T>(sel)} /** Queries multiple elements matching selectors **/ export function qsa<T extends Element = HTMLElement>(x: string | ParentNode, y?: string): T[] { const [base, sel] = und(y) ? [doc,x as string] : [x as ParentNode,y as string] return Array.from(base.querySelectorAll<T>(sel))} /** Adds an event listener to a target **/ export function ael<K extends keyof WindowEventMap,>(target: Window, type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): Window export function ael<K extends keyof DocumentEventMap,>(target: Document, type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): Document export function ael<K extends keyof HTMLElementEventMap, T extends HTMLElement,>(target: T, type: K, listener: (this: T, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): T export function ael<T extends EventTarget,>(target: T, type: string, listener: Function, options?: boolean | AddEventListenerOptions): T export function ael(target: EventTarget | string, type: string | Function, listener?: Function | boolean | AddEventListenerOptions, options?: boolean | AddEventListenerOptions): EventTarget { let base: EventTarget = doc let eventType: string let callback: Function let eventOptions: boolean | AddEventListenerOptions | undefined if (typeof target === 'string') { eventType = target callback = type as Function eventOptions = listener as (boolean | AddEventListenerOptions | undefined)} else { base = target eventType = type as string callback = listener as Function eventOptions = options} if (typeof callback === 'function') { base.addEventListener(eventType, callback as EventListener, eventOptions)} else { console.warn('ael: Provided listener is not a function.', { target, type, listener, options })} return base} /** Adds a DOMContentLoaded listener **/ export const onl=(f: Function): Document =>ael(doc, 'DOMContentLoaded',f) /** Adds a click listener to an element **/ export const onc=<T extends EventTarget,>(e: T, f: Function): T =>ael(e,'click',f) /** Creates and returns a mutation observer **/ export function mob(cb: MutationCallback, e?: Node, c?: MutationObserverInit): MutationObserver { let res = new MutationObserver(cb) if (e) res.observe(e,c||{childList:true, subtree:true}) return res} // #endregion // #region glosh import * as sh from './sh.mjs' // Keep as .mjs if that's the compiled output expected elsewhere type ShModule = typeof sh; export function glosh(ctx0?: any, quiet?: boolean) { let ctx = ctx0 ?? globalThis as any // Use 'as any' for broad compatibility if needed if (!quiet) { log(`installing sh.* on ${ctx?.constructor?.name ?? ctx}:`) log(`[${oks(sh).join(' ')}]`)} (oks(sh) as Array<keyof ShModule>).forEach(k => { if (k in sh) { ctx[k] = sh[k]; } })} (globalThis as any).$sh=glosh // $sh() will make all global glosh(glosh,true) // register $sh.xxx globally, quietly // #endregion

Read the original on gist.github.com ↗