mirror of
https://github.com/MercuryWorkshop/dreamlandjs.git
synced 2025-05-16 15:40:01 -04:00
support 2 way data binding to components
This commit is contained in:
parent
0196435107
commit
01129edb48
4 changed files with 75 additions and 31 deletions
9
AliceJS.d.ts
vendored
9
AliceJS.d.ts
vendored
|
@ -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<T> = { readonly __symbol: unique symbol, readonly __signature: T };
|
||||
|
||||
declare function use(sink: any, mapping?: (...args: any[]) => any): AliceJSReferenceSink;
|
||||
declare function use<T>(sink: T | any, mapping?: (...args: any[]) => any): AliceJSReferenceSink<T>;
|
||||
declare function useValue<T>(sink: AliceJSReferenceSink<T>): T;
|
||||
|
||||
type Stateful<T> = T & { readonly symbol: unique symbol };
|
||||
|
||||
|
||||
declare function stateful<T>(target: T): Stateful<T>;
|
||||
|
||||
declare function handle(references: AliceJSReferenceSink, callback: (value: any) => void): void;
|
||||
declare function handle<T>(references: AliceJSReferenceSink<T>, callback: (value: T) => void): void;
|
||||
|
||||
declare function css(strings: TemplateStringsArray, ...values: any): string;
|
||||
declare var styled: { new: typeof css };
|
||||
|
|
78
AliceJS.js
78
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 <input>
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
<script src="../dist/index.js"></script>
|
||||
<script src="../index.js"></script>
|
||||
|
||||
<script src="lib/index.js"></script>
|
||||
</head>
|
||||
|
|
|
@ -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 (
|
||||
<div css={css} class="box">
|
||||
|
@ -62,7 +63,7 @@ function ToDoList() {
|
|||
return (
|
||||
<div class="box" css={css}>
|
||||
<div>
|
||||
<input $value={use(this.text)} on:change={() => addTask()} />
|
||||
<input bind:value={use(this.text)} on:change={() => addTask()} />
|
||||
<button on:click={() => addTask()}>Add Task</button>
|
||||
</div>
|
||||
<div for={use(this.tasks)} do={(task, i) =>
|
||||
|
@ -99,16 +100,22 @@ function Index() {
|
|||
}
|
||||
`;
|
||||
|
||||
this.c = 5;
|
||||
|
||||
this.counterobj;
|
||||
|
||||
return (
|
||||
<div css={css}>
|
||||
<div className={"as"}>
|
||||
<div>
|
||||
<h1>AliceJS Examples</h1>
|
||||
<p>Some examples of AliceJS components. Code is in examples/</p>
|
||||
</div>
|
||||
<examples>
|
||||
<Counter />
|
||||
<Counter a="b" bind:this={use(this.counterobj)} bind:counter={use(this.c)} />
|
||||
<ToDoList />
|
||||
</examples>
|
||||
stuff: {use(this.counterobj.counter)}
|
||||
<button on:click={() => this.counterobj.counter++}>as</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue