commit b3a0d962e2e91b727656eca93631ac0fdf6817e1
parent dd2da6550fa4ae379570821d64d6a5a66cc3468e
Author: typable <contact@typable.dev>
Date: Sat, 10 Dec 2022 11:18:14 +0100
Switched to typescript
Diffstat:
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[];