diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/rx.ts | 138 | 
1 files 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<any>  +  = false +  | undefined +  | string +  | number +  | Tag +  | WithState<any>    | Array<Html> -  | Rx<Html>  +  | Rx<Html>  interface Tag {    type: 'Tag' @@ -21,10 +21,10 @@ interface Tag {    onunmount?: (element: Element) => void  } -interface WithVar<A> { -  type: 'WithVar' +interface WithState<A> { +  type: 'WithState'    init: A -  getChildren: (v: Var<A>, update: (f: (value: A) => A) => void) => Html +  getChildren: (v: Var<A>) => 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<A>(init: A, getChildren: (v: Var<A>, update: (f: (value: A) => A) => void) => Html): WithVar<A> { +export function withState<A>(init: A, getChildren: (v: Var<A>) => Html): WithState<A> {    return { -    type: 'WithVar', +    type: 'WithState',      init,      getChildren    } @@ -112,14 +112,31 @@ export class Rx<A> {    }  } +class Pure<A> extends Rx<A> { +  readonly type: 'Pure' +  readonly value: A + +  constructor(value: A) { +    super() +    this.type = 'Pure' +    this.value = value +  } +} + +export function pure<A>(value: A): Rx<A> { +  return new Pure(value) +} +  class Var<A> extends Rx<A> {    readonly type: 'Var'    readonly id: string +  readonly update: (f: (value: A) => A) => void -  constructor(id: string) { +  constructor(id: string, update: (v: Var<A>) => ((f: ((value: A) => A)) => void)) {      super()      this.id = id      this.type = 'Var' +    this.update = update(this)    }  } @@ -149,6 +166,28 @@ class FlatMap<A, B> extends Rx<B> {    }  } +export function sequence<A>(xs: Array<Rx<A>>): Sequence<A> { +  return new Sequence<A>(xs) +} + +export function sequence2<A>(xs: Array<Rx<A>>): Rx<Array<A>> { +  return xs.reduce( +    (acc: Rx<Array<A>>, x: Rx<A>) => acc.flatMap(ys => x.map(y => [y, ...ys])), +    new Pure([]) +  ) +} + +class Sequence<A> extends Rx<Array<A>> { +  readonly type: 'Sequence' +  readonly xs: Array<Rx<A>> + +  constructor(xs: Array<Rx<A>>) { +    super() +    this.type = 'Sequence' +    this.xs = xs +  } +} +  // Mount  export function mount(html: Html): Cancelable { @@ -172,7 +211,7 @@ class State {    }    register<A>(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<A>(state: State, rx: Rx<A>, effect: (value: A) => void): Cancelable { -  if (isVar(rx)) { +function rxRun<A>( +  state: State, +  rx: Rx<A>, +  effect: (value: A) => void +): Cancelable { +  if (isPure<A>(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<A>(state: State, rx: Rx<A>, 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<A>(x: any): x is Rx<A> { -  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<A>(x: any): x is Pure<A> { +  return x.type === 'Pure'  }  function isVar<A>(x: any): x is Var<A> { @@ -251,11 +323,15 @@ function isVar<A>(x: any): x is Var<A> {  }  function isMap<A, B>(x: any): x is Map<A, B> { -    return x.type === "Map" +  return x.type === "Map"  }  function isFlatMap<A, B>(x: any): x is FlatMap<A, B> { -    return x.type === "FlatMap" +  return x.type === "FlatMap" +} + +function isSequence<A>(x: any): x is Sequence<A> { +  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<A>(x: any): x is Tag {    return x !== undefined && x.type === "Tag"  } -function isWithVar<A>(x: any): x is WithVar<A> { -  return x !== undefined && x.type === "WithVar" +function isWithState<A>(x: any): x is WithState<A> { +  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()  | 
