commit 3e73dc1735bddaf786cb3101e5a1778d4752a964
parent b38eec5eb647373a391c87bdc426fddf2c49441c
Author: typable <contact@typable.dev>
Date: Fri, 7 Apr 2023 01:46:52 +0200
feat: Implemented component syntax
Diffstat:
M | README.md | | | 38 | +++++++++++++++++++++++++++++--------- |
M | lib.ts | | | 74 | +++++++++++++++++++++++++++++++++++++++----------------------------------- |
M | types.ts | | | 8 | ++++++-- |
3 files changed, 74 insertions(+), 46 deletions(-)
diff --git a/README.md b/README.md
@@ -6,20 +6,40 @@ Reactive template literals for React
```javascript
import figure from '...';
-const { html, dyn } = figure({ createElement });
+// initialize figure
+const { dist } = figure({ createElement });
-const global = createContext({});
+// add your components to a bundle
+const ElementBundle = {
+ button: Button,
+};
-function App() {
- const [name, setName] = useState('world');
+// add your bundles to the dictionary
+const html = dist({
+ el: ElementBundle,
+});
- const context = {};
+function App() {
+ return html`
+ <main>
+ <el:button
+ type="primary"
+ on:click=${() => console.log('It works!')}
+ >
+ <span>Click me</span>
+ </el:button>
+ </main>
+ `;
+}
+function Button() {
return html`
- ${dyn(global.Provider, { value: context }), html`
- <h1>Hello ${name}!</h1>
- <p>Some description text.</p>
- `}
+ <button
+ on:click=${props?.onClick}
+ class="btn btn--${props?.type}"
+ >
+ ${props?.children}
+ </button>
`;
}
diff --git a/lib.ts b/lib.ts
@@ -1,4 +1,4 @@
-import { Options, Props, ReactElement, ReactFunction, Refs, Slices, Values } from './types.ts';
+import { Options, Props, ReactElement, ReactFunction, Refs, Slices, Values, Dict } from './types.ts';
export default function figure({ createElement }: Options) {
@@ -8,24 +8,34 @@ export default function figure({ createElement }: Options) {
let count = 0;
/**
- * Converts the template literal HTML syntax into React elements.
- * @param {Slices} slices - The template literal slices
- * @param {Values} values - The template literal values
- * @return {ReactElement[]} The converted HTML as React elements.
+ * Returns the a function for rendering HTML.
+ * @param {Dict} dict - The dictionary for resolving React components.
+ * @return {Function} The function for rendering HTML.
*/
- function html(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!';
+ function dict(dict?: Dict): (slices: Slices, ...values: Values) => ReactElement[] {
+
+ /**
+ * Converts the template literal HTML syntax into React elements.
+ * @param {Slices} slices - The template literal slices
+ * @param {Values} values - The template literal values
+ * @return {ReactElement[]} The converted HTML as React elements.
+ */
+ function html(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!';
+ }
+ // collect all nodes from head and body
+ const nodes = [...dom.head.childNodes, ...dom.body.childNodes];
+ return nodes.map((node) => render(node, refs, dict ?? {}));
}
- // collect all nodes from head and body
- const nodes = [...dom.head.childNodes, ...dom.body.childNodes];
- return nodes.map((node) => render(node, refs));
+
+ return html;
}
/**
@@ -89,7 +99,7 @@ export default function figure({ createElement }: Options) {
* @param {Refs} refs - The values mapped to there references
* @return {ReactElement[]} The converted HTML node as React element
*/
- function render(node: Node, refs: Refs): ReactElement[] {
+ function render(node: Node, refs: Refs, dict: Dict): ReactElement[] {
if (node.nodeType === Node.TEXT_NODE) {
const text = node as Text;
if (text.textContent == null) {
@@ -103,7 +113,7 @@ export default function figure({ createElement }: Options) {
return [];
}
const element = node as HTMLElement;
- const tag = element.tagName;
+ const tag = element.tagName.toLowerCase();
const props: Props = {};
// iterate over each attribute and add it to the props
for (const attribute of element.attributes) {
@@ -157,22 +167,16 @@ export default function figure({ createElement }: Options) {
}
const children: ReactElement[] = [];
// recursively render all child nodes
- (node.childNodes ?? []).forEach((child) => children.push(...render(child, refs)));
- return [createElement(tag, props, ...children)];
- }
-
- /**
- * Creates a dynamic component with its own state.
- * Should be used if the component is statefull (contains hooks).
- *
- * @param {ReactFunction} element - The string containing references
- * @param {Props} props - The component properties
- * @param {ReactElement[]} children - The child elements
- * @return {ReactElement} The created React component
- */
- function dyn(element: ReactFunction, props?: Props, children?: ReactElement[]): ReactElement {
- return createElement(element, props, children);
+ (node.childNodes ?? []).forEach((child) => children.push(...render(child, refs, dict)));
+ const domain = tag.split(':');
+ // look up tag name in dictionary
+ // deno-lint-ignore no-explicit-any
+ const component: ReactFunction | undefined = domain.reduce((dict: any, level) => {
+ return dict?.[level];
+ }, dict);
+ // use React component or tag name
+ return [createElement(component ?? tag, props, ...children)];
}
- return { html, dyn };
+ return { dict };
}
diff --git a/types.ts b/types.ts
@@ -1,9 +1,13 @@
export type ReactElement = unknown;
-export type ReactFunction = unknown;
-export type CreateElement = (element: ReactFunction, props?: Props, ...children: ReactElement[]) => ReactElement;
+export type ReactFunction = (props: unknown) => ReactElement;
+export type CreateElement = (element: ReactFunction | string, 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[];
+
+export interface Dict {
+ [key: string]: ReactFunction | Dict;
+}