Last active
December 6, 2024 01:30
-
-
Save creationix/596fbf41f57ec90b24b98a9b638841f1 to your computer and use it in GitHub Desktop.
Revisions
-
creationix renamed this gist
Dec 6, 2024 . 1 changed file with 52 additions and 26 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -7,6 +7,8 @@ ////////////////////////////////////// // Modern version using TypeScript and ES6 features export type Refs = Record<string, HTMLElement> export type Component<T extends unknown[]> = (...args: T) => Value export type Value = string | Element | Group | HTMLElement | Text | DocumentFragment export type Group = (Element | Group)[] @@ -16,40 +18,36 @@ export type ElementWithProps = [string, Properties, ...Value[]] export type ElementComponent<T extends unknown[]> = [Component<T>, ...T] interface Properties { [key: string]: string | boolean | ((node: HTMLElement) => void) | Record<string, string> $?: (node: HTMLElement) => void style?: Record<string, string> | string } const CLASS_MATCH = /\.[^.#$]+/g const ID_MATCH = /#[^.#$]+/ const REF_MATCH = /\$[^.#$]+/ const TAG_MATCH = /^[^.#$]+/ export function renderText(text: string): Text { return document.createTextNode(text) } export function renderGroup(json: Group, refs?: Refs): DocumentFragment { const frag = document.createDocumentFragment() for (const child of json) { frag.appendChild(renderAny(child, refs)) } return frag } export function renderComponent<T extends unknown[]>( json: ElementComponent<T>, refs?: Refs, ): HTMLElement | Text | DocumentFragment { const [createComponent, ...state] = json return renderAny(createComponent(...state), refs) } export function renderElement(json: ElementNoProps | ElementWithProps, refs?: Refs): HTMLElement { // Create Elements const first = json[0] const match = first.match(TAG_MATCH) @@ -63,6 +61,10 @@ export function domBuilder(json: Value): HTMLElement | Text | DocumentFragment { if (id) { el.setAttribute('id', id[0].substr(1)) } const ref = first.match(REF_MATCH) if (refs && ref) { refs[ref[0].substring(1)] = el } // Optionally apply properties and append children let children: Value[] @@ -74,11 +76,35 @@ export function domBuilder(json: Value): HTMLElement | Text | DocumentFragment { children = json.slice(1) } for (const child of children) { el.appendChild(renderAny(child, refs)) } return el } // This is a simple dom builder that takes a json structure and returns a dom tree. export function renderAny(json: Value, refs?: Refs): HTMLElement | Text | DocumentFragment { if (!Array.isArray(json)) { // Pass through html elements, text nodes, and fragments as-is if (isNode(json)) { return json } return renderText(String(json)) } // Handle Groups if (isGroup(json)) { return renderGroup(json, refs) } // Handle Components if (isComponent(json)) { return renderComponent(json, refs) } return renderElement(json, refs) } function applyProps(node: HTMLElement, attrs: Properties) { for (const [key, value] of Object.entries(attrs)) { if (typeof value === 'string') { -
creationix revised this gist
Dec 5, 2024 . 1 changed file with 6 additions and 6 deletions.There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -7,13 +7,13 @@ ////////////////////////////////////// // Modern version using TypeScript and ES6 features export type Component<T extends unknown[]> = (...args: T) => Value export type Value = string | Element | Group | HTMLElement | Text | DocumentFragment export type Group = (Element | Group)[] export type Element = ElementNoProps | ElementWithProps | ElementComponent<unknown[]> export type ElementNoProps = [string, ...Value[]] export type ElementWithProps = [string, Properties, ...Value[]] export type ElementComponent<T extends unknown[]> = [Component<T>, ...T] interface Properties { [key: string]: string | boolean | ((node: HTMLElement) => void) | Record<string, string> $: (node: HTMLElement) => void @@ -112,7 +112,7 @@ function isGroup(value: unknown[]): value is Group { return value.length === 0 || Array.isArray(value[0]) } function isComponent(value: unknown[]): value is ElementComponent<unknown[]> { return typeof value[0] === 'function' } -
creationix created this gist
Dec 5, 2024 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,121 @@ ////////////////////////////////////// // // // JS domBuilder Library // // // // Tim Caswell <[email protected]> // // // ////////////////////////////////////// // Modern version using TypeScript and ES6 features export type Value = string | Element | Component<unknown[]> | Group | HTMLElement | Text | DocumentFragment export type Group = (Element | Component<unknown[]> | Group)[] export type Element = ElementNoProps | ElementWithProps export type ElementNoProps = [string, ...Value[]] export type ElementWithProps = [string, Properties, ...Value[]] export type Component<T extends unknown[]> = [(...args: T) => Value, ...T] interface Properties { [key: string]: string | boolean | ((node: HTMLElement) => void) | Record<string, string> $: (node: HTMLElement) => void style: Record<string, string> | string } const CLASS_MATCH = /\.[^.#$]+/g const ID_MATCH = /#[^.#$]+/ const TAG_MATCH = /^[^.#$]+/ // This is a simple dom builder that takes a json structure and returns a dom tree. export function domBuilder(json: Value): HTMLElement | Text | DocumentFragment { if (!Array.isArray(json)) { // Pass through html elements, text nodes, and fragments as-is if (isNode(json)) { return json } // Render Text Nodes return document.createTextNode(String(json)) } // Handle Groups if (isGroup(json)) { const frag = document.createDocumentFragment() for (const child of json) { frag.appendChild(domBuilder(child)) } return frag } // Handle Components if (isComponent(json)) { const [createComponent, ...state] = json return domBuilder(createComponent(...state)) } // Create Elements const first = json[0] const match = first.match(TAG_MATCH) const tag = match ? match[0] : 'div' const el = document.createElement(tag) const classes = first.match(CLASS_MATCH) if (classes) { el.setAttribute('class', classes.map(stripFirst).join(' ')) } const id = first.match(ID_MATCH) if (id) { el.setAttribute('id', id[0].substr(1)) } // Optionally apply properties and append children let children: Value[] if (isElementWithProps(json)) { const [, props, ...rest] = json applyProps(el, props) children = rest } else { children = json.slice(1) } for (const child of children) { el.appendChild(domBuilder(child)) } return el } function applyProps(node: HTMLElement, attrs: Properties) { for (const [key, value] of Object.entries(attrs)) { if (typeof value === 'string') { node.setAttribute(key, value) } else if (value === true) { node.setAttribute(key, key) } else if (value === false) { node.removeAttribute(key) } else if (key === 'style') { for (const [k, v] of Object.entries(value)) { node.style[k] = v } } else if (key !== '$') { node[key] = value } } if ('$' in attrs) { attrs.$(node) } } function stripFirst(part: string): string { return part.substring(1) } function isNode(value: Value): value is HTMLElement | Text | DocumentFragment { return value instanceof HTMLElement || value instanceof window.Text || value instanceof window.DocumentFragment } function isGroup(value: unknown[]): value is Group { return value.length === 0 || Array.isArray(value[0]) } function isComponent(value: unknown[]): value is Component<unknown[]> { return typeof value[0] === 'function' } function isElementWithProps(value: unknown[]): value is ElementWithProps { return value.length > 1 && typeof value[1] === 'object' }