Skip to content

Instantly share code, notes, and snippets.

@StoneyEagle
Last active June 21, 2025 13:42
Show Gist options
  • Save StoneyEagle/5df834788300bd403d9a203b33ed8467 to your computer and use it in GitHub Desktop.
Save StoneyEagle/5df834788300bd403d9a203b33ed8467 to your computer and use it in GitHub Desktop.
Typesafe create element
export interface CreateElement<E extends Element> {
addClasses: (names: string[]) => AddClasses<E>;
appendTo: <P extends Element>(parent: P) => AddClassesReturn<E>;
prependTo: <P extends Element>(parent: P) => AddClassesReturn<E>;
get: () => E;
}
export interface AddClasses<E extends Element> {
appendTo: <P extends Element>(parent: P) => AddClassesReturn<E>;
prependTo: <P extends Element>(parent: P) => AddClassesReturn<E>;
addClasses: (names: string[]) => AddClasses<E>;
get: () => E;
}
export interface AddClassesReturn<E extends Element> {
addClasses: (names: string[]) => AddClasses<E>;
get: () => E;
}
/**
* Creates a new HTML element of the specified type and assigns the given ID to it.
* @param type - The type of the HTML element to create.
* @param id - The ID to assign to the new element.
* @param unique - Whether to use an existing element with the specified ID if it already exists.
* @returns An object with four methods:
* - `addClasses`: adds the specified CSS class names to the element's class list and returns the functions recursively.
* - `appendTo`: appends the element to a parent element and and returns addClasses and get methods.
* - `prependTo`: prepends the element to a parent element and returns addClasses and get methods.
* - `get`: returns the created element.
*/
function createElement<K extends keyof HTMLElementTagNameMap>(type: K, id: string, unique?: boolean) {
let el: HTMLElementTagNameMap[K];
if (unique) {
el = (document.getElementById(id) ?? document.createElement(type)) as HTMLElementTagNameMap[K];
} else {
el = document.createElement(type);
}
el.id = id;
return {
addClasses: (names: string[]) => addClasses(el, names),
appendTo: <T extends Element>(parent: T) => {
parent.appendChild(el);
return {
addClasses: (names: string[]) => addClasses(el, names),
get: () => el,
};
},
prependTo: <T extends Element>(parent: T) => {
parent.prepend(el);
return {
addClasses: (names: string[]) => addClasses(el, names),
get: () => el,
};
},
get: () => el,
};
}
/**
* Adds the specified CSS class names to the given element's class list.
*
* @param el - The element to add the classes to.
* @param names - An array of CSS class names to add.
* @returns An object with three methods:
* - `appendTo`: appends the element to a parent element and returns addClasses and get methods.
* - `prependTo`: prepends the element to a parent element and returns addClasses and get methods.
* - `get`: returns the created element.
* @template T - The type of the element to add the classes to.
*/
function addClasses<T extends Element>(el: T, names: string[]): AddClasses<T> {
for (const name of names.filter(Boolean)) {
el.classList?.add(name.trim());
}
return {
appendTo: <T extends Element>(parent: T) => {
parent.appendChild(el);
return {
addClasses: (names: string[]) => addClasses(el, names),
get: () => el,
};
},
prependTo: <T extends Element>(parent: T) => {
parent.prepend(el);
return {
addClasses: (names: string[]) => addClasses(el, names),
get: () => el,
};
},
addClasses: (names: string[]) => addClasses(el, names),
get: () => el,
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment