소스 검색

fixed tagged component states

Matheus Giovani 3 년 전
부모
커밋
59c6e5d045

+ 26 - 3
packages/renderer/src/core/Component.ts

@@ -47,6 +47,13 @@ export interface IComponent<
     mounted?: (this: Component) => any;
 }
 
+/**
+ * Components also are records, because they carry any type of data.
+ */
+export interface Component extends Record<string, any> {
+
+}
+
 export class Component {
     public static create<
         TMethods extends Record<string, CallableFunction>,
@@ -55,6 +62,11 @@ export class Component {
         return new Component(component) as (Component & TMethods);
     }
 
+    /**
+     * The component parent to this component.
+     */
+    public $parent: Component|null = null;
+
     /**
      * The state related to this component.
      */
@@ -75,8 +87,6 @@ export class Component {
      */
     public $refs: Record<string, HTMLElement> = {};
 
-    protected parser: DOMParser;
-
     /**
      * If it's the first time that the component is being rendered.
      */
@@ -85,7 +95,7 @@ export class Component {
     /**
      * The virtual DOM renderer instance.
      */
-    protected renderer = new Renderer(this);
+    public renderer = new Renderer(this);
 
     constructor(
         /**
@@ -139,6 +149,19 @@ export class Component {
         }
     }
 
+    /**
+     * The root component.
+     */
+    public get $root() {
+        let parent = this.$parent;
+
+        while(parent?.$parent !== null) {
+            parent = parent?.$parent;
+        }
+
+        return parent;
+    }
+
     /**
      * Enqueues a function to be executed in the next queue tick.
      * @param callback — The callback to be executed.

+ 27 - 18
packages/renderer/src/core/vdom/Renderer.ts

@@ -22,6 +22,8 @@ export type TRendererNodes = RendererNode | ConditionalNode | LoopNode;
  * Thanks, alpine.js!
  */
 export class Renderer {
+    vnode: string | VirtualDOM.VTree | (string | VirtualDOM.VTree)[];
+    rendererNode: ConditionalNode | LoopNode | RendererNode<VirtualDOM.VText | VirtualDOM.VComment | VirtualDOM.VNode | VirtualDOM.Widget | VirtualDOM.Thunk>;
     /**
      * Creates a renderer node from a virtual DOM node.
      * @param node The original virtual DOM node.
@@ -145,33 +147,40 @@ export class Renderer {
     public rendered = false;
 
     /**
-     * Renders the virtual dom for the first time.
+     * Renders the virtual dom into a virtual DOM node.
      * @returns 
      */
-    public async render() {
+    public async renderToNode() {
         const tick = this.nextTick(async () => {
-            debug("first render");
-
             const vdom = this.component.$component.render({ h });
             const node = Renderer.createNode(vdom, null, this);
-            const result = await walk(node, this.generateScope());
-            const vnode = result.toVNode();
+            this.rendererNode = await walk(node, this.generateScope());
+        });
 
-            try {
-                this.container = create(vnode as VirtualDOM.VNode, {
-                    warn: true
-                });
+        await this.waitForTick(tick);
 
-                this.rendered = true;
+        return this.rendererNode;
+    }
 
-                debug("first render ended");
-            } catch(e) {
-                Debugger.error("an exception ocurred while rendering a component %O", vnode);
-                throw e;
-            }
-        });
+    /**
+     * Renders the virtual dom for the first time.
+     * @returns 
+     */
+    public async render() {
+        this.vnode = (await this.renderToNode()).toVNode();
 
-        await this.waitForTick(tick);
+        try {
+            this.container = create(this.vnode as VirtualDOM.VNode, {
+                warn: true
+            });
+
+            this.rendered = true;
+
+            debug("first render ended");
+        } catch(e) {
+            Debugger.error("an exception ocurred while rendering component %O", this.vnode);
+            throw e;
+        }
 
         return this.container;
     }

+ 13 - 17
packages/renderer/src/core/vdom/directives/Component.ts

@@ -1,11 +1,8 @@
 import { Component } from "../../../core/Component";
-import { Renderer } from "../../../core/vdom/Renderer";
 import Debugger from "../../../util/Debugger";
-import h from "virtual-dom/h";
 import { directive } from "../../../model/Directive";
 import { evaluateLater } from "../../../model/Evaluator";
 import { effect } from "../../../model/Reactivity";
-import { walk } from "../../../model/NodeWalker";
 
 /**
  * @directive x-component
@@ -23,26 +20,25 @@ directive("component", async (node, { expression, scope }) => {
             // Remove the component attribute
             node.removeAttribute("x-component");
 
-            // Pass all attributes as $props to the scope
-            const newScope = scope;
-
+            // Parse all attributes into the component state
             const attrs = node.getAttributesAndProps();
             for(let key in attrs) {
-                scope[key] = attrs[key];
+                component.$state[key] = evaluateLater(attrs[key]);
+
+                if (component.$state[key] instanceof Function) {
+                    component.$state[key] = await component.$state[key](scope);
+                }
             }
 
+            // Set the parent component
+            component.$parent = scope.$component as Component;
+
+            Debugger.debug("%s scope is %O", expression, component);
+
             // Remove the original attribute from the node
-            node.replaceWith(
-                await walk(
-                    Renderer.createNode(
-                        component.$component.render({ h }),
-                        node.parent,
-                        node.renderer
-                    ), newScope
-                )
-            );
+            node.replaceWith(await component.renderer.renderToNode());
         } catch(e) {
-            console.warn("[pupper.js] failed to bind property:");
+            console.warn("pupper.js has failed to create component:");
             console.error(e);
         }
     });

+ 4 - 1
packages/renderer/src/core/vdom/directives/HTML.ts

@@ -2,6 +2,8 @@ import { directive } from "../../../model/Directive";
 import { evaluateLater } from "../../../model/Evaluator";
 import { effect } from "../../../model/Reactivity";
 import { RendererNode } from "../../../model/vdom/RendererNode";
+import Debugger from "../../../util/Debugger";
+
 import dom2vdom from "@pupperjs/dom2vdom";
 
 import h from "virtual-dom/h";
@@ -36,7 +38,8 @@ directive("html", async (node, { expression, scope }) => {
             node.parent.children[insertPosition].replaceWith(replacement);
             node.parent.setDirty();
         } catch(e) {
-            console.warn("[pupper.js] failed to set inner HTML:");
+            console.warn("pupper.js has failed to set inner HTML:");
+            Debugger.warn("scope was %O", scope);
             console.error(e);
         }
     });

+ 1 - 1
packages/renderer/src/model/Evaluator.ts

@@ -33,7 +33,7 @@ export function evaluateString<TExpressionResult = any>(expression: string) {
         func = SafeAsyncFunction(rightSideSafeExpression);
     } catch (err) {
         console.warn("pupper.js warning: invalid expression \"" + rightSideSafeExpression + "\"\n", err);
-        return undefined;
+        return () => rightSideSafeExpression;
     }
 
     evaluatorMemo[expression] = func;

+ 2 - 2
packages/renderer/src/model/Reactivity.ts

@@ -67,8 +67,8 @@ export function reactive(obj: TReactiveObj) {
                 return target[property];
             }
 
-            // Ignore functions
-            if (typeof target[property] === "function") {
+            // Ignore functions and proxies
+            if (typeof target[property] === "function" || property === ProxySymbol) {
                 return target[property];
             }