NEWS: Welcome to my new homepage! <3

Switched to typescript - figure - Unnamed repository; edit this file 'description' to name the repository.

figure

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit b3a0d962e2e91b727656eca93631ac0fdf6817e1
parent dd2da6550fa4ae379570821d64d6a5a66cc3468e
Author: typable <contact@typable.dev>
Date:   Sat, 10 Dec 2022 11:18:14 +0100

Switched to typescript

Diffstat:
Adeno.json | 11+++++++++++
Mexamples/counter.html | 2+-
Mexamples/picker.html | 2+-
Dlib.js | 159-------------------------------------------------------------------------------
Alib.ts | 199+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dmods.js | 32--------------------------------
Atypes.ts | 11+++++++++++
7 files changed, 223 insertions(+), 193 deletions(-)

diff --git a/deno.json b/deno.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "dom.asynciterable", + "deno.ns", + "deno.unstable" + ] + } +} diff --git a/examples/counter.html b/examples/counter.html @@ -43,7 +43,7 @@ } const app = document.querySelector('#app'); - figure({createElement}); + figure({ createElement }); render(createElement(Counter), app); </script> diff --git a/examples/picker.html b/examples/picker.html @@ -85,7 +85,7 @@ } const app = document.querySelector('#app'); - figure({createElement}); + figure({ createElement }); render(createElement(Picker), app); </script> diff --git a/lib.js b/lib.js @@ -1,159 +0,0 @@ -const parser = new DOMParser(); -let count = 1000; -let create = null; - -const figure = ({createElement}) => { - create = createElement; -} - -const html = (strings, ...props) => { - const elements = parse(strings, ...props); - if(elements.length === 0) { - throw 'No DOM element was returned!'; - } - if(elements.length > 1) { - console.warn('Only one DOM element can be returned!'); - } - return elements[0]; -} - -const parse = (strings, ...props) => { - const [html, refs] = compose(strings, props); - let dom; - try { - dom = parser.parseFromString(html, 'text/html'); - } - catch(error) { - console.error(error); - throw 'Invalid DOM structure!'; - } - const nodes = [...dom.head.childNodes, ...dom.body.childNodes]; - return nodes.map((node) => render(node, refs)); -} - -/** - * Joins the template literal strings together and replaces the properties with references. - * The properties are being mapped to there corresponding references and with the populated - * HTML string returned. - * - * @param {string[]} strings - The template literal strings - * @param {any[]} props - The template literal properties - * @return {any[]} The joined HTML string and the properties mapped to there references. - */ -const compose = (strings, props) => { - if(strings === undefined) { - // handles dyn function without body - return ['', {}]; - } - const refs = {}; - let string = ''; - for(let i = 0; i < strings.length; i++) { - string += strings[i]; - if(props[i] !== undefined) { - const uid = `$seg-${count++}`; - refs[uid] = props[i]; - string += uid ?? ''; - } - } - return [string.trim(), refs]; -} - -/** - * Injects the properties into the corresponding reference locations of the string. - * - * @param {string} string - The string containing references - * @param {any[]} refs - The properties mapped to there references - * @return {any[]} The string populate with the passed properties - */ -const feed = (string, refs) => { - const expr = /\$seg-\d+/g; - let values = []; - let match = null; - let last = 0; - while((match = expr.exec(string)) !== null) { - const index = match.index; - const uid = match[0]; - values.push(string.substring(last, index)); - let value = refs[uid]; - if(value instanceof Function) { - value = value(); - } - values.push(value); - last = index + uid.length; - } - values.push(string.substring(last)); - return values; -} - -const dyn = (element, props) => { - return (strings, ...childProps) => { - return create(element, props, ...parse(strings, ...childProps)); - }; -} - -const render = (node, refs) => { - if(node.nodeType === Node.TEXT_NODE) { - return feed(node.textContent, refs); - } - if(node.nodeType === Node.COMMENT_NODE) { - return []; - } - const tag = node.tagName; - const attributes = {}; - for(const attribute of node.attributes) { - const key = attribute.name; - const value = attribute.textContent; - let isDynamic = false; - for(const ref in refs) { - if(value === ref) { - let match = null; - if((match = /^@(\w+)$/.exec(key)) !== null) { - const event = match[1]; - attributes[`on${event.substring(0, 1).toUpperCase()}${event.substring(1)}`] = refs[ref]; - } - else if((match = /^\[(\w+)\]$/.exec(key)) !== null) { - const property = match[1]; - attributes[property] = refs[ref]; - } - else { - attributes[key] = refs[ref]; - } - isDynamic = true; - break; - } - } - if(!isDynamic) { - if(key === 'style') { - const styles = {}; - for(const item of value.split(';')) { - if(item.trim().length === 0) { - break; - } - let [key, value] = item.split(':'); - key = key.trim(); - let match = null; - if((match = /-(\w)/.exec(key)) !== null) { - const char = match[1]; - key = key.replace(`-${char}`, char.toUpperCase()); - } - value = value.trim(); - styles[key] = feed(value, refs).join(''); - } - attributes[key] = styles; - } - else { - attributes[key] = feed(value, refs).join(''); - } - } - } - const children = []; - (node.childNodes ?? []).forEach((child) => children.push(...render(child, refs))); - return [create(tag, attributes, ...children)]; -} - -import {defCss, defStyle} from './mods.js'; - -const css = defCss({html, compose, feed}); -const style = defStyle({html, compose, feed}); - -export { figure, html, dyn, css, style }; diff --git a/lib.ts b/lib.ts @@ -0,0 +1,199 @@ +import { CreateElement, Option, Options, Props, ReactElement, ReactFunction, Refs, Slices, Values } from './types.ts'; + +const parser = new DOMParser(); +let count = 1000; +let create: Option<CreateElement> = null; + +function figure({ createElement }: Options): void { + create = createElement; +} + +function html(slices: Slices, ...values: Values): ReactElement { + const elements = parse(slices, ...values); + if (elements.length === 0) { + throw 'No DOM element was returned!'; + } + if (elements.length > 1) { + console.warn('Only one DOM element can be returned!'); + } + return elements[0]; +} + +function parse(slices: Slices, ...values: Values): ReactElement[] { + const [html, refs] = compose(slices, values); + let dom; + try { + dom = parser.parseFromString(html, 'text/html'); + } + catch (error) { + console.error(error); + throw 'Invalid DOM structure!'; + } + const nodes = [...dom.head.childNodes, ...dom.body.childNodes]; + return nodes.map((node) => render(node, refs)); +} + +/** + * Joins the template literal slices together and replaces the values with references. + * The values are being mapped to there corresponding references and with the populated + * HTML string returned. + * + * @param {string[]} slices - The template literal slices + * @param {any[]} values - The template literal values + * @return {any[]} The joined HTML string and the values mapped to there references. + */ +function compose(slices: Slices, values: Values): [string, Refs] { + if (slices == null) { + // handles dyn function without body + return ['', {}]; + } + const refs: Refs = {}; + let slice = ''; + for (let i = 0; i < slices.length; i++) { + slice += slices[i]; + if (values[i] != null) { + const uid = `$seg-${count++}`; + refs[uid] = values[i]; + slice += uid ?? ''; + } + } + return [slice.trim(), refs]; +} + +/** + * Injects the values into the corresponding reference locations of the string. + * + * @param {string} slice - The string containing references + * @param {any[]} refs - The values mapped to there references + * @return {any[]} The string populated with the passed values + */ +function feed(slice: string, refs: Refs): ReactElement[] { + const expr = /\$seg-\d+/g; + const elements: ReactElement[] = []; + let match = null; + let last = 0; + while ((match = expr.exec(slice)) !== null) { + const index = match.index; + const uid = match[0]; + elements.push(slice.substring(last, index)); + let value = refs[uid]; + if (value instanceof Function) { + value = value(); + } + elements.push(value); + last = index + uid.length; + } + elements.push(slice.substring(last)); + return elements; +} + +function dyn(element: ReactFunction, props: Props): (slices: Slices, ...values: Values) => ReactElement { + return (slices: Slices, ...values: Values) => { + if (create == null) { + throw 'Invalid state! Figure was not initialized!'; + } + return create(element, props, ...parse(slices, ...values)); + }; +} + +function render(node: Node, refs: Refs): ReactElement[] { + if (node.nodeType === Node.TEXT_NODE) { + const text = node as Text; + if (text.textContent == null) { + return []; + } + return feed(text.textContent, refs); + } + if (node.nodeType === Node.COMMENT_NODE) { + return []; + } + const element = node as HTMLElement; + const tag = element.tagName; + const attributes: Props = {}; + for (const attribute of element.attributes) { + const key = attribute.name; + const slice = attribute.textContent; + if (slice == null) { + continue; + } + let isDynamic = false; + for (const ref in refs) { + if (slice === ref) { + let match = null; + if ((match = /^@(\w+)$/.exec(key)) !== null) { + const event = match[1]; + attributes[`on${event.substring(0, 1).toUpperCase()}${event.substring(1)}`] = refs[ref]; + } + else if ((match = /^\[(\w+)\]$/.exec(key)) !== null) { + const property = match[1]; + attributes[property] = refs[ref]; + } + else { + attributes[key] = refs[ref]; + } + isDynamic = true; + break; + } + } + if (!isDynamic) { + if (key === 'style') { + const styles: Record<string, string> = {}; + for (const item of slice.split(';')) { + if (item.trim().length === 0) { + break; + } + let [key, value] = item.split(':'); + key = key.trim(); + let match = null; + if ((match = /-(\w)/.exec(key)) !== null) { + const char = match[1]; + key = key.replace(`-${char}`, char.toUpperCase()); + } + value = value.trim(); + styles[key] = feed(value, refs).join(''); + } + attributes[key] = styles; + } + else { + attributes[key] = feed(slice, refs).join(''); + } + } + } + const children: ReactElement[] = []; + (node.childNodes ?? []).forEach((child) => children.push(...render(child, refs))); + if (create == null) { + throw 'Invalid state! Figure was not initialized!'; + } + return [create(tag, attributes, ...children)]; +} + +function css(slices: Slices, ...values: Values): ReactElement { + const [css, refs] = compose(slices, values); + return html` + <style type="text/css"> + ${feed(css, refs).join('')} + </style> + `; +} + +function style(slices: Slices, ...values: Values): Record<string, string> { + const [css, refs] = compose(slices, values); + const styles: Record<string, string> = {}; + for (const item of css.split(';')) { + if (item.trim().length === 0) { + break; + } + let [key, value] = item.split(':'); + key = key.trim(); + let match = null; + if ((match = /-(\w)/.exec(key)) !== null) { + const char = match[1]; + key = key.replace(`-${char}`, char.toUpperCase()); + } + value = value.trim(); + styles[key] = feed(value, refs).join(''); + } + return styles; +} + +export { figure, html, dyn, css, style }; diff --git a/mods.js b/mods.js @@ -1,32 +0,0 @@ -export const defCss = ({html, compose, feed}) => { - return (strings, ...props) => { - const [css, refs] = compose(strings, props); - return html` - <style type="text/css"> - ${feed(css, refs).join('')} - </style> - `; - }; -} - -export const defStyle = ({compose, feed}) => { - return (strings, ...props) => { - const [css, refs] = compose(strings, props); - const styles = {}; - for(const item of css.split(';')) { - if(item.trim().length === 0) { - break; - } - let [key, value] = item.split(':'); - key = key.trim(); - let match = null; - if((match = /-(\w)/.exec(key)) !== null) { - const char = match[1]; - key = key.replace(`-${char}`, char.toUpperCase()); - } - value = value.trim(); - styles[key] = feed(value, refs).join(''); - } - return styles; - }; -} diff --git a/types.ts b/types.ts @@ -0,0 +1,11 @@ +export type Option<T> = T | null | undefined; + +export type ReactElement = unknown; +export type ReactFunction = unknown; +export type CreateElement = (element: ReactFunction, props?: Props, ...children: ReactElement[]) => ReactElement; + +export type Options = { createElement: CreateElement }; +export type Refs = Record<string, unknown>; +export type Props = Record<string, unknown>; +export type Slices = TemplateStringsArray; +export type Values = unknown[];