diff --git a/AliceJS.d.ts b/AliceJS.d.ts index 0ac09d1..4a3b00e 100644 --- a/AliceJS.d.ts +++ b/AliceJS.d.ts @@ -4,20 +4,21 @@ declare namespace JSX { declare function h( type: string, - props: { [index: string]: any } | null, + props?: { [index: string]: any } | null, ...children: (HTMLElement | string)[] ): Node; -type AliceJSReferenceSink = { readonly __symbol: unique symbol }; +type AliceJSReferenceSink = { readonly __symbol: unique symbol, readonly __signature: T }; -declare function use(sink: any, mapping?: (...args: any[]) => any): AliceJSReferenceSink; +declare function use(sink: T | any, mapping?: (...args: any[]) => any): AliceJSReferenceSink; +declare function useValue(sink: AliceJSReferenceSink): T; type Stateful = T & { readonly symbol: unique symbol }; declare function stateful(target: T): Stateful; -declare function handle(references: AliceJSReferenceSink, callback: (value: any) => void): void; +declare function handle(references: AliceJSReferenceSink, callback: (value: T) => void): void; declare function css(strings: TemplateStringsArray, ...values: any): string; declare var styled: { new: typeof css }; diff --git a/AliceJS.js b/AliceJS.js index 5a4d4c3..ea391b7 100644 --- a/AliceJS.js +++ b/AliceJS.js @@ -30,7 +30,7 @@ Object.defineProperty(window, "use", { }; } }); -Object.assign(window, { stateful, handle, css, styled: { new: css } }); +Object.assign(window, { stateful, handle, useValue, css, styled: { new: css } }); // This wraps the target in a proxy, doing 2 things: // - whenever a property is accessed, update the reference stack @@ -122,6 +122,11 @@ export function handle(references, callback) { } } +export function useValue(references) { + let reference = references[references.length - 1]; + return reference.proxy[reference.property]; +} + // Hack to skip use() when there's only one possible property Object.defineProperty(window, "h", { get: () => { @@ -143,9 +148,40 @@ Object.defineProperty(window, "h", { function h(type, props, ...children) { if (typeof type === "function") { let newthis = stateful({}); - let component = type.bind(newthis); - return component(); + for (const name in props) { + const references = props[name]; + if (isAJSReferences(references) && name.startsWith("bind:")) { + let reference = references[references.length - 1]; + const propname = name.substring(5); + if (propname == "this") { + reference.proxy[reference.property] = newthis; + } else { + // component two way data binding!! (exact same behavior as svelte:bind) + let isRecursive = false; + + handle(references, value => { + if (isRecursive) { + isRecursive = false; + return; + } + isRecursive = true; + newthis[propname] = value + }); + handle(use(newthis[propname]), value => { + if (isRecursive) { + isRecursive = false; + return; + } + isRecursive = true; + reference.proxy[reference.property] = value; + }); + } + delete props[name]; + } + } + + return type.apply(newthis, [props]); } @@ -310,23 +346,28 @@ function h(type, props, ...children) { } - // two-way bind an - useProp("$value", references => { - if (!isAJSReferences(references)) return; - if (!(elm instanceof HTMLInputElement)) return; - - handle(references, value => elm.value = value); - let reference = references[references.length - 1]; - elm.addEventListener("change", () => { - reference.proxy[reference.property] = elm.value; - }) - }); - // insert an element at the end useProp("after", callback => { addChild(callback()); }) + for (const name in props) { + const references = props[name]; + if (isAJSReferences(references) && name.startsWith("bind:")) { + let reference = references[references.length - 1]; + const propname = name.substring(5); + if (propname == "this") { + reference.proxy[reference.property] = elm; + } else if (propname == "value") { + handle(references, value => elm.value = value); + elm.addEventListener("change", () => { + reference.proxy[reference.property] = elm.value; + }) + } + delete props[name]; + } + } + // apply the non-reactive properties for (const name in props) { const prop = props[name]; @@ -355,7 +396,7 @@ function JSXAddAttributes(elm, name, prop) { return; } - if (typeof prop === "function" && name === "@mount") { + if (typeof prop === "function" && name === "mount") { prop(elm); return; } @@ -386,11 +427,6 @@ function JSXAddAttributes(elm, name, prop) { observer.observe(elm); return; } - if (name.startsWith("bind:")) { - const propname = name.substring(5); - prop[propname] = elm; - return; - } elm.setAttribute(name, prop); } diff --git a/examples/example.html b/examples/example.html index ba8ec5e..60a9dbd 100644 --- a/examples/example.html +++ b/examples/example.html @@ -6,7 +6,7 @@ - + diff --git a/examples/index.tsx b/examples/index.tsx index 1a603f1..0c1f72a 100644 --- a/examples/index.tsx +++ b/examples/index.tsx @@ -1,4 +1,5 @@ -function Counter() { +function Counter(a) { + console.log(a); let css = styled.new` self { display: flex; @@ -21,7 +22,7 @@ function Counter() { } `; - this.counter = 0; + this.counter ??= 0; return (
@@ -62,7 +63,7 @@ function ToDoList() { return (
- addTask()} /> + addTask()} />
@@ -99,16 +100,22 @@ function Index() { } `; + this.c = 5; + + this.counterobj; + return ( -
+

AliceJS Examples

Some examples of AliceJS components. Code is in examples/

- + + stuff: {use(this.counterobj.counter)} +
); }