commit 0cd4b7e389e852cb230031c3b53ad236448a71f0
parent 71bcd6376c673c2b386604e00d1e55a030d892b3
Author: typable <contact@typable.dev>
Date: Sat, 17 Sep 2022 01:26:43 +0200
Restructured code
Diffstat:
M | README.md | | | 4 | ++-- |
M | examples/picker.html | | | 11 | +++++------ |
A | mods.js | | | 32 | ++++++++++++++++++++++++++++++++ |
A | segment.js | | | 141 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
D | tlx.js | | | 138 | ------------------------------------------------------------------------------- |
5 files changed, 180 insertions(+), 146 deletions(-)
diff --git a/README.md b/README.md
@@ -1,2 +1,2 @@
-# tlx
-Template literals syntax for React Hooks
+# segment
+Reactive template literals for React
diff --git a/examples/picker.html b/examples/picker.html
@@ -4,9 +4,7 @@
import {useState, createElement} from 'https://cdn.skypack.dev/react';
import {render} from 'https://cdn.skypack.dev/react-dom';
- import {createTlx, css} from '../tlx.js';
-
- const tlx = createTlx(createElement);
+ import {segment, html, css} from '../segment.js';
const Range = ({index, value, setValue}) => {
const style = css`
@@ -30,7 +28,7 @@
}
`;
- return tlx`
+ return html`
<div class="range v-${index}-${value}">
${style}
<input
@@ -73,7 +71,7 @@
const hex = (dec) => dec.toString(16).toUpperCase().padStart(2, '0');
- return tlx`
+ return html`
<div class="picker">
${style}
<div class="preview"></div>
@@ -85,8 +83,9 @@
</div>
`;
}
-
+
const app = document.querySelector('#app');
+ segment({createElement});
render(createElement(Picker), app);
</script>
diff --git a/mods.js b/mods.js
@@ -0,0 +1,32 @@
+export const defCss = ({html, compose, feed}) => {
+ return (parts, ...props) => {
+ const [css, refs] = compose(parts, props);
+ return html`
+ <style type="text/css">
+ ${feed(css, refs).join('')}
+ </style>
+ `;
+ };
+}
+
+export const defStyle = ({compose, feed}) => {
+ return (parts, ...props) => {
+ const [css, refs] = compose(parts, 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/segment.js b/segment.js
@@ -0,0 +1,141 @@
+const parser = new DOMParser();
+let count = 1000;
+let create = null;
+
+const segment = ({createElement}) => {
+ create = createElement;
+}
+
+const html = (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 node = dom.body.childNodes[0] ?? dom.head.childNodes[0];
+ const elements = render(node, refs);
+ 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];
+}
+
+/**
+ * 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) => {
+ const refs = {};
+ let string = '';
+ for(let i = 0; i < strings.length; i++) {
+ string += strings[i];
+ if(props[i] !== undefined) {
+ const uid = `$tlx-${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 = /\$tlx-\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 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 {
+ 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 { segment, html, css, style };
diff --git a/tlx.js b/tlx.js
@@ -1,138 +0,0 @@
-const parser = new DOMParser();
-let counter = 1000;
-let tlx = null;
-
-export const createTlx = (createElement) => {
- function parse(parts, ...props) {
- const [html, refs] = ltr(parts, props);
- const dom = parser.parseFromString(html, 'text/html');
- const node = dom.body.childNodes[0] ?? dom.head.childNodes[0];
- const elements = render(node, refs);
- if(elements.length !== 1) {
- throw 'invalid VDOM structure!';
- }
- return elements[0];
- }
-
- function render(node, refs) {
- if(node.nodeType === Node.TEXT_NODE) {
- return apply(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.substr(0, 1).toUpperCase()}${event.substr(1)}`] = 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] = apply(value, refs).join('');
- }
- attributes[key] = styles;
- }
- else {
- attributes[key] = apply(value, refs).join('');
- }
- }
- }
- const children = [];
- (node.childNodes ?? []).forEach((child) => children.push(...render(child, refs)));
- return [createElement(tag, attributes, ...children)];
- }
-
- tlx = parse;
- return tlx;
-}
-
-export const css = (parts, ...props) => {
- const [css, refs] = ltr(parts, props);
- return tlx`
- <style type="text/css">
- ${apply(css, refs).join('')}
- </style>
- `;
-}
-
-export const style = (parts, ...props) => {
- const [css, refs] = ltr(parts, 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] = apply(value, refs).join('');
- }
- return styles;
-}
-
-function apply(value, refs) {
- let values = [];
- const expr = /\$tlx-\d+/g;
- let match = null;
- let last = 0;
- while((match = expr.exec(value)) !== null) {
- const index = match.index;
- values.push(value.substring(last, index));
- let refValue = refs[match[0]];
- if(refValue instanceof Function) {
- refValue = refValue();
- }
- values.push(refValue);
- last = index + match[0].length;
- }
- values.push(value.substring(last));
- return values;
-}
-
-function ltr(parts, props) {
- let string = '';
- const refs = {};
- for(let i = 0; i < parts.length; i++) {
- string += parts[i];
- if(props[i] !== undefined) {
- const id = `$tlx-${counter}`;
- refs[id] = props[i];
- string += id ?? '';
- counter++;
- }
- }
- return [string.trim(), refs];
-}