From bb058d35c7110dcffe31b4e7afabbe26673a43a8 Mon Sep 17 00:00:00 2001 From: Joris Date: Sun, 19 Feb 2023 14:38:48 +0100 Subject: Upgrade Rx to 2.0.0 --- src/lib/rx.ts | 138 ++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 110 insertions(+), 28 deletions(-) diff --git a/src/lib/rx.ts b/src/lib/rx.ts index 3f3b8d9..c46c2f5 100644 --- a/src/lib/rx.ts +++ b/src/lib/rx.ts @@ -1,16 +1,16 @@ -// [1.1.0] 2023-02-13 +// Rx 2.0.0 // Html export type Html - = false - | undefined - | string - | number - | Tag - | WithVar + = false + | undefined + | string + | number + | Tag + | WithState | Array - | Rx + | Rx interface Tag { type: 'Tag' @@ -21,10 +21,10 @@ interface Tag { onunmount?: (element: Element) => void } -interface WithVar { - type: 'WithVar' +interface WithState { + type: 'WithState' init: A - getChildren: (v: Var, update: (f: (value: A) => A) => void) => Html + getChildren: (v: Var) => Html } interface Attributes { @@ -42,8 +42,8 @@ type AttributeValue function isHtml(x: any): x is Html { return (typeof x === 'string' || typeof x === 'number' - || isTag(x) - || isWithVar(x) + || isTag(x) + || isWithState(x) || isRx(x) || Array.isArray(x)) } @@ -90,9 +90,9 @@ export function h( } } -export function withVar(init: A, getChildren: (v: Var, update: (f: (value: A) => A) => void) => Html): WithVar { +export function withState(init: A, getChildren: (v: Var) => Html): WithState { return { - type: 'WithVar', + type: 'WithState', init, getChildren } @@ -112,14 +112,31 @@ export class Rx { } } +class Pure extends Rx { + readonly type: 'Pure' + readonly value: A + + constructor(value: A) { + super() + this.type = 'Pure' + this.value = value + } +} + +export function pure(value: A): Rx { + return new Pure(value) +} + class Var extends Rx { readonly type: 'Var' readonly id: string + readonly update: (f: (value: A) => A) => void - constructor(id: string) { + constructor(id: string, update: (v: Var) => ((f: ((value: A) => A)) => void)) { super() this.id = id this.type = 'Var' + this.update = update(this) } } @@ -149,6 +166,28 @@ class FlatMap extends Rx { } } +export function sequence(xs: Array>): Sequence { + return new Sequence(xs) +} + +export function sequence2(xs: Array>): Rx> { + return xs.reduce( + (acc: Rx>, x: Rx) => acc.flatMap(ys => x.map(y => [y, ...ys])), + new Pure([]) + ) +} + +class Sequence extends Rx> { + readonly type: 'Sequence' + readonly xs: Array> + + constructor(xs: Array>) { + super() + this.type = 'Sequence' + this.xs = xs + } +} + // Mount export function mount(html: Html): Cancelable { @@ -172,7 +211,7 @@ class State { } register(initValue: A) { - const v = new Var(this.varCounter.toString()) + const v = new Var(this.varCounter.toString(), v => (f => this.update(v, f))) this.varCounter += BigInt(1) this.state[v.id] = { value: initValue, @@ -220,8 +259,15 @@ const voidRemove = () => {} // Rx run -function rxRun(state: State, rx: Rx, effect: (value: A) => void): Cancelable { - if (isVar(rx)) { +function rxRun( + state: State, + rx: Rx, + effect: (value: A) => void +): Cancelable { + if (isPure(rx)) { + effect(rx.value) + return voidCancel + } else if (isVar(rx)) { const cancel = state.subscribe(rx, effect) effect(state.get(rx)) return cancel @@ -237,13 +283,39 @@ function rxRun(state: State, rx: Rx, effect: (value: A) => void): Cancelab cancel2() cancel1() } + } else if (isSequence(rx)) { + const cancels = Array(rx.xs.length).fill(voidCancel) + const xs = Array(rx.xs.length).fill(undefined) + let initEnded = false + + rx.xs.forEach((rxChild, i) => { + cancels[i] = rxRun( + state, + rxChild, + (value: A) => { + xs[i] = value + if (initEnded) { + // @ts-ignore + effect(xs) + } + } + ) + }) + // @ts-ignore + effect(xs) + initEnded = true + return () => cancels.forEach(cancel => cancel()) } else { throw new Error(`Unrecognized rx: ${rx}`) } } function isRx(x: any): x is Rx { - return x !== undefined && x.type !== undefined && (x.type === "Var" || x.type === "Map" || x.type === "FlatMap") + return x !== undefined && x.type !== undefined && (x.type === "Var" || x.type === "Map" || x.type === "FlatMap" || x.type === 'Sequence' || x.type === 'Pure') +} + +function isPure(x: any): x is Pure { + return x.type === 'Pure' } function isVar(x: any): x is Var { @@ -251,11 +323,15 @@ function isVar(x: any): x is Var { } function isMap(x: any): x is Map { - return x.type === "Map" + return x.type === "Map" } function isFlatMap(x: any): x is FlatMap { - return x.type === "FlatMap" + return x.type === "FlatMap" +} + +function isSequence(x: any): x is Sequence { + return x.type === "Sequence" } // Append @@ -322,10 +398,10 @@ function appendChild(state: State, element: Element, child: Html, lastAdded?: No remove: () => element.removeChild(childElement), lastAdded: childElement, } - } else if (isWithVar(child)) { + } else if (isWithState(child)) { const { init, getChildren } = child const v = state.register(init) - const children = getChildren(v, f => state.update(v, f)) + const children = getChildren(v) const appendRes = appendChild(state, element, children) return { cancel: () => { @@ -374,8 +450,8 @@ function isTag(x: any): x is Tag { return x !== undefined && x.type === "Tag" } -function isWithVar(x: any): x is WithVar { - return x !== undefined && x.type === "WithVar" +function isWithState(x: any): x is WithState { + return x !== undefined && x.type === "WithState" } function appendNode(base: Element, node: Node, lastAdded?: Node) { @@ -387,11 +463,17 @@ function appendNode(base: Element, node: Node, lastAdded?: Node) { } function setAttribute(state: State, element: Element, key: string, attribute: AttributeValue) { - if (attribute === undefined || attribute === false) { + if (attribute === undefined) { // Do nothing } else if (attribute === true) { // @ts-ignore - element[key] = "true" + element[key] = 'true' + } else if (attribute === false) { + // @ts-ignore + if (key in element) { + // @ts-ignore + element[key] = false + } } else if (typeof attribute === "number") { // @ts-ignore element[key] = attribute.toString() -- cgit v1.2.3