aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/lib/rx.ts138
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()