From a3aff2f248daee9c7fba46d7b25dc33f739a3af2 Mon Sep 17 00:00:00 2001 From: CoolElectronics Date: Fri, 23 Feb 2024 10:54:18 -0500 Subject: [PATCH] rewrite array reactivity entirely and fix if statemensts also add fragemetns lol --- AliceJS.d.ts | 5 ++++- README.md | 2 +- js.js | 63 ++++++++++++++++++++++++++++++---------------------- store.js | 2 +- 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/AliceJS.d.ts b/AliceJS.d.ts index 5fd3814..644ee89 100644 --- a/AliceJS.d.ts +++ b/AliceJS.d.ts @@ -2,7 +2,7 @@ declare namespace JSX { export type IntrinsicElements = { [index: string]: any }; - type ElementType = string | Component; + type ElementType = Fragment | string | Component; type Element = DLElement; interface ElementAttributesProperty { @@ -42,6 +42,9 @@ type DLCSS = string; declare var $el: HTMLElement; +type Fragment = { readonly fragment: unique symbol }; +declare var Fragment: Fragment; + interface Element { $: OuterComponentTypes & { [index: string | symbol]: any } } diff --git a/README.md b/README.md index 8b5c0a5..8734ed7 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ To get started with AliceJS, add this to the compileroptions of your `tsconfig.j ```json "jsx":"react", "jsxFactory":"h", -"jsxFragmentFactory":"YOU_CANT_USE_FRAGMENTS", +"jsxFragmentFactory":"Fragment", "types": ["@mercuryworkshop/alicejs"], ``` and run `npm install @mercuryworkshop/alicejs` diff --git a/js.js b/js.js index 3452061..109c382 100644 --- a/js.js +++ b/js.js @@ -1,3 +1,5 @@ +export const Fragment = Symbol(); + // whether to return the true value from a stateful object or a "trap" containing the pointer let __use_trap = false; @@ -30,13 +32,14 @@ Object.defineProperty(window, "use", { }; } }); -Object.assign(window, { isDLPtr, h, stateful, handle, useValue, $if }); +Object.assign(window, { isDLPtr, h, stateful, handle, useValue, $if, Fragment }); const TARGET = Symbol(); const PROXY = Symbol(); const STEPS = Symbol(); const LISTENERS = Symbol(); +const IF = Symbol(); const TRAPS = new Map; // This wraps the target in a proxy, doing 2 things: // - whenever a property is accessed, return a "trap" that catches and records accessors @@ -90,18 +93,8 @@ export function $if(condition, then, otherwise) { otherwise ??= document.createTextNode(""); then ??= document.createTextNode(""); if (!isDLPtr(condition)) return condition ? then : otherwise; - let root = then; - handle(condition, v => { - if (v) { - root.replaceWith(then); - root = then; - } else { - root.replaceWith(otherwise); - root = otherwise; - } - }) - return root; + return { [IF]: condition, then, otherwise }; } // This lets you subscribe to a stateful object @@ -164,7 +157,9 @@ export function useValue(references) { // Actual JSX factory. Responsible for creating the HTML elements and all of the *reactive* syntactic sugar export function h(type, props, ...children) { + if (type === Fragment) return children; if (typeof type === "function") { + // functional components. create the stateful object let newthis = stateful(Object.create(type.prototype)); for (const name in props) { @@ -222,8 +217,26 @@ export function h(type, props, ...children) { let xmlns = props?.xmlns; const elm = xmlns ? document.createElementNS(xmlns, type) : document.createElement(type); + for (const child of children) { - JSXAddChild(child, elm.appendChild.bind(elm)); + let cond = !isDLPtr(child) && child[IF]; + if (cond) { + let appended = null; + handle(cond, v => { + let before = appended?.[0]?.previousSibling; + if (appended) + appended.forEach(a => a.remove()); + + appended = JSXAddChild(v ? child.then : child.otherwise, el => { + if (before) { + before.after(el) + before = el; + } + else elm.appendChild(el) + }) + }) + } else + JSXAddChild(child, elm.appendChild.bind(elm)); } if (!props) return elm; @@ -308,21 +321,16 @@ function JSXAddChild(child, cb) { if (isDLPtr(child)) { let appended = []; handle(child, (val) => { - if (appended.length > 1) { - // this is why we don't encourage arrays (jank) - appended.forEach(n => n.remove()); - appended = JSXAddChild(val, cb); - } else if (appended.length > 0) { - let old = appended[0]; - appended = JSXAddChild(val, cb); - if (appended[0]) { - old.replaceWith(appended[0]) - } else { - old.remove(); + let v = appended[0]?.previousSibling; + appended.forEach(n => n.remove()); + appended = JSXAddChild(val, el => { + if (v) { + v.after(el); + v = el; } - } else { - appended = JSXAddChild(val, cb); - } + else + cb(el); + }); }); } else if (child instanceof Node) { cb(child); @@ -332,6 +340,7 @@ function JSXAddChild(child, cb) { for (const childchild of child) { elms = elms.concat(JSXAddChild(childchild, cb)); } + if (!elms[0]) elms = JSXAddChild("", cb); return elms; } else { let node = document.createTextNode(child); diff --git a/store.js b/store.js index 57b6531..89486fc 100644 --- a/store.js +++ b/store.js @@ -4,8 +4,8 @@ export function $store(target, ident, type) { target = JSON.parse(stored) ?? target; addEventListener("beforeunload", () => { - localStorage.setItem(JSON.stringify(target)); console.info("[dreamland.js]: saving " + ident); + localStorage.setItem(ident, JSON.stringify(target)); }); return stateful(target);