Created
August 9, 2026 21:17
-
-
Save StoneyEagle/4287cf60b2b105586e556ccda1b4b45f to your computer and use it in GitHub Desktop.
Chainable DOM element builder utility (fluent create/addClasses/appendTo)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * A chainable object returned after creating a new (or reusing an existing) | |
| * DOM element, allowing further configuration or insertion into the document. | |
| * | |
| * @template T - The type of DOM element being built. | |
| */ | |
| export interface CreateElement<T extends Element> { | |
| /** | |
| * Returns the underlying DOM element. | |
| * | |
| * @returns The element being built. | |
| */ | |
| get: () => T; | |
| /** | |
| * Adds one or more CSS classes to the element. | |
| * | |
| * @param names - The class names to add. Falsy entries are skipped. | |
| * @returns A chainable object for further configuration. | |
| */ | |
| addClasses: (names: string[]) => AddClasses<T>; | |
| /** | |
| * Appends the element as the last child of the given parent. | |
| * | |
| * @param parent - The parent element to append to. | |
| * @returns A chainable object for further configuration. | |
| */ | |
| appendTo: (parent: Element) => AppendTo<T>; | |
| /** | |
| * Inserts the element as the first child of the given parent. | |
| * | |
| * @param parent - The parent element to prepend to. | |
| * @returns A chainable object for further configuration. | |
| */ | |
| prependTo: (parent: Element) => AppendTo<T>; | |
| /** | |
| * Sets an HTML attribute on the element. | |
| * | |
| * @param name - The attribute name. | |
| * @param value - The attribute value. | |
| * @returns A chainable object for further configuration. | |
| */ | |
| setAttribute: (name: string, value: string) => AddClasses<T>; | |
| /** | |
| * Sets an inline CSS custom property or style property on the element, | |
| * if it is an `HTMLElement`. | |
| * | |
| * @param name - The CSS property name. | |
| * @param value - The CSS property value. | |
| * @returns A chainable object for further configuration. | |
| */ | |
| setProperty: (name: string, value: string) => AddClasses<T>; | |
| } | |
| /** | |
| * A chainable object returned after adding classes to an element, allowing | |
| * further configuration or insertion into the document. | |
| * | |
| * @template T - The type of DOM element being built. | |
| */ | |
| export interface AddClasses<T extends Element> { | |
| /** | |
| * Returns the underlying DOM element. | |
| * | |
| * @returns The element being built. | |
| */ | |
| get: () => T; | |
| /** | |
| * Appends the element as the last child of the given parent. | |
| * | |
| * @param parent - The parent element to append to. | |
| * @returns A chainable object for further configuration. | |
| */ | |
| appendTo: (parent: Element) => AppendTo<T>; | |
| /** | |
| * Inserts the element as the first child of the given parent. | |
| * | |
| * @param parent - The parent element to prepend to. | |
| * @returns A chainable object for further configuration. | |
| */ | |
| prependTo: (parent: Element) => AppendTo<T>; | |
| /** | |
| * Adds one or more CSS classes to the element. | |
| * | |
| * @param names - The class names to add. Falsy entries are skipped. | |
| * @returns A chainable object for further configuration. | |
| */ | |
| addClasses: (names: string[]) => AddClasses<T>; | |
| /** | |
| * Sets an HTML attribute on the element. | |
| * | |
| * @param name - The attribute name. | |
| * @param value - The attribute value. | |
| * @returns A chainable object for further configuration. | |
| */ | |
| setAttribute: (name: string, value: string) => AddClasses<T>; | |
| /** | |
| * Sets an inline CSS custom property or style property on the element, | |
| * if it is an `HTMLElement`. | |
| * | |
| * @param name - The CSS property name. | |
| * @param value - The CSS property value. | |
| * @returns A chainable object for further configuration. | |
| */ | |
| setProperty: (name: string, value: string) => AddClasses<T>; | |
| } | |
| /** | |
| * A chainable object returned after inserting an element into the document, | |
| * allowing further class configuration. | |
| * | |
| * @template T - The type of DOM element being built. | |
| */ | |
| export interface AppendTo<T extends Element> { | |
| /** | |
| * Returns the underlying DOM element. | |
| * | |
| * @returns The element being built. | |
| */ | |
| get: () => T; | |
| /** | |
| * Adds one or more CSS classes to the element. | |
| * | |
| * @param names - The class names to add. Falsy entries are skipped. | |
| * @returns A chainable object for further configuration. | |
| */ | |
| addClasses: (names: string[]) => AddClasses<T>; | |
| } | |
| /** | |
| * Creates a new DOM element of the given tag type, or reuses an existing | |
| * element with a matching id when `unique` is set, and returns a chainable | |
| * builder for configuring and inserting it. | |
| * | |
| * @param type - The HTML tag name to create, e.g. `'div'` or `'span'`. | |
| * @param id - The id to assign to the element (or to look up when `unique` is set). | |
| * @param unique - When true, reuses an existing element with the given id instead of creating a new one. | |
| * @returns A chainable builder for the created or reused element. | |
| * @template K - The HTML tag name key, used to infer the concrete element type. | |
| */ | |
| export function createElement<K extends keyof HTMLElementTagNameMap>( | |
| type: K, | |
| id: string, | |
| unique?: boolean, | |
| ): CreateElement<HTMLElementTagNameMap[K]> { | |
| let el: HTMLElementTagNameMap[K]; | |
| if (unique) { | |
| const existing = document.querySelector<HTMLElementTagNameMap[K]>(`#${CSS.escape(id)}`); | |
| el = existing ?? document.createElement(type); | |
| } | |
| else { | |
| el = document.createElement(type); | |
| } | |
| if (!el.id) | |
| el.id = id; | |
| return { | |
| get: () => el, | |
| addClasses: (names: string[]) => addClasses(el, names), | |
| appendTo: (parent: Element) => { | |
| parent.appendChild(el); | |
| return makeAppendTo(el); | |
| }, | |
| prependTo: (parent: Element) => { | |
| parent.insertBefore(el, parent.firstChild); | |
| return makeAppendTo(el); | |
| }, | |
| setAttribute: (name: string, value: string) => { | |
| el.setAttribute(name, value); | |
| return makeAddClasses(el); | |
| }, | |
| setProperty: (name: string, value: string) => { | |
| if (el instanceof HTMLElement) | |
| el.style.setProperty(name, value); | |
| return makeAddClasses(el); | |
| }, | |
| }; | |
| } | |
| /** | |
| * Adds one or more CSS classes to an element and returns a chainable builder | |
| * for further configuration. | |
| * | |
| * @param el - The element to add classes to. | |
| * @param names - The class names to add. Falsy entries are skipped. | |
| * @returns A chainable builder for the element. | |
| * @template T - The type of DOM element. | |
| */ | |
| export function addClasses<T extends Element>(el: T, names: string[]): AddClasses<T> { | |
| for (const name of names) { | |
| if (name) | |
| el.classList.add(name); | |
| } | |
| return makeAddClasses(el); | |
| } | |
| /** | |
| * Removes one or more CSS classes from an element. | |
| * | |
| * @param el - The element to remove classes from. | |
| * @param names - The class names to remove. Falsy entries are skipped. | |
| * @returns The same element, for further use. | |
| * @template T - The type of DOM element. | |
| */ | |
| export function removeClasses<T extends Element>(el: T, names: string[]): T { | |
| for (const name of names) { | |
| if (name) | |
| el.classList.remove(name); | |
| } | |
| return el; | |
| } | |
| const SVG_NS = 'http://www.w3.org/2000/svg'; | |
| /** | |
| * Creates a new SVG root element with the given id and viewBox. | |
| * | |
| * @param id - The id to assign to the SVG element. | |
| * @param viewBox - The value of the `viewBox` attribute, e.g. `'0 0 24 24'`. | |
| * @returns The created `SVGSVGElement`. | |
| */ | |
| export function createSVG(id: string, viewBox: string): SVGSVGElement { | |
| const svg = document.createElementNS(SVG_NS, 'svg'); | |
| svg.setAttribute('id', id); | |
| svg.setAttribute('viewBox', viewBox); | |
| svg.setAttribute('xmlns', SVG_NS); | |
| return svg; | |
| } | |
| /** | |
| * Creates a new accessible `<button>` element with a click handler. | |
| * | |
| * @param id - The id to assign to the button. | |
| * @param label - The accessible label, used for both `aria-label` and `title`. | |
| * @param onClick - The click event handler to attach to the button. | |
| * @returns The created `HTMLButtonElement`. | |
| */ | |
| export function createButton(id: string, label: string, onClick: (event: Event) => void): HTMLButtonElement { | |
| const button = document.createElement('button'); | |
| button.id = id; | |
| button.type = 'button'; | |
| button.setAttribute('aria-label', label); | |
| button.title = label; | |
| button.addEventListener('click', onClick); | |
| return button; | |
| } | |
| /** | |
| * Builds a chainable object for an element that has just been inserted into | |
| * the document, exposing only class configuration. | |
| * | |
| * @param el - The element that was inserted. | |
| * @returns A chainable builder exposing `get` and `addClasses`. | |
| * @template T - The type of DOM element. | |
| */ | |
| function makeAppendTo<T extends Element>(el: T): AppendTo<T> { | |
| return { | |
| get: () => el, | |
| addClasses: (names: string[]) => addClasses(el, names), | |
| }; | |
| } | |
| /** | |
| * Builds a chainable object for an element that has just had classes, | |
| * attributes, or properties applied, exposing the full set of remaining | |
| * configuration and insertion methods. | |
| * | |
| * @param el - The element being configured. | |
| * @returns A chainable builder exposing insertion and further configuration methods. | |
| * @template T - The type of DOM element. | |
| */ | |
| function makeAddClasses<T extends Element>(el: T): AddClasses<T> { | |
| return { | |
| get: () => el, | |
| appendTo: (parent: Element) => { | |
| parent.appendChild(el); | |
| return makeAppendTo(el); | |
| }, | |
| prependTo: (parent: Element) => { | |
| parent.insertBefore(el, parent.firstChild); | |
| return makeAppendTo(el); | |
| }, | |
| addClasses: (names: string[]) => addClasses(el, names), | |
| setAttribute: (name: string, value: string) => { | |
| el.setAttribute(name, value); | |
| return makeAddClasses(el); | |
| }, | |
| setProperty: (name: string, value: string) => { | |
| if (el instanceof HTMLElement) | |
| el.style.setProperty(name, value); | |
| return makeAddClasses(el); | |
| }, | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment