aboutsummaryrefslogtreecommitdiff
path: root/frontend/ts/src/lib/rx.ts
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/ts/src/lib/rx.ts')
-rw-r--r--frontend/ts/src/lib/rx.ts781
1 files changed, 781 insertions, 0 deletions
diff --git a/frontend/ts/src/lib/rx.ts b/frontend/ts/src/lib/rx.ts
new file mode 100644
index 0000000..5edd3c1
--- /dev/null
+++ b/frontend/ts/src/lib/rx.ts
@@ -0,0 +1,781 @@
+// Rx 3.0.0
+
+// Html
+
+export type Html
+ = false
+ | undefined
+ | null
+ | string
+ | number
+ | Tag
+ | WithState<any>
+ | WithState2<any, any>
+ | WithState3<any, any, any>
+ | WithState4<any, any, any, any>
+ | WithState5<any, any, any, any, any>
+ | WithState6<any, any, any, any, any, any>
+ | WithState7<any, any, any, any, any, any, any>
+ | Array<Html>
+ | Rx<Html>
+
+interface Tag {
+ type: 'Tag'
+ tagName: string
+ isSvg: boolean
+ attributes: Attributes
+ children?: Array<Html>
+ onmount?: (element: Element) => void
+ onunmount?: (element: Element) => void
+}
+
+interface WithState<A> {
+ type: 'WithState'
+ init: A
+ getChildren: (v: Var<A>) => Html
+}
+
+interface WithState2<A, B> {
+ type: 'WithState2'
+ init: [A, B]
+ getChildren: (v1: Var<A>, v2: Var<B>) => Html
+}
+
+interface WithState3<A, B, C> {
+ type: 'WithState3'
+ init: [A, B, C]
+ getChildren: (v1: Var<A>, v2: Var<B>, v3: Var<C>) => Html
+}
+
+interface WithState4<A, B, C, D> {
+ type: 'WithState4'
+ init: [A, B, C, D]
+ getChildren: (v1: Var<A>, v2: Var<B>, v3: Var<C>, v4: Var<D>) => Html
+}
+
+interface WithState5<A, B, C, D, E> {
+ type: 'WithState5'
+ init: [A, B, C, D, E]
+ getChildren: (v1: Var<A>, v2: Var<B>, v3: Var<C>, v4: Var<D>, v5: Var<E>) => Html
+}
+
+interface WithState6<A, B, C, D, E, F> {
+ type: 'WithState6'
+ init: [A, B, C, D, E, F]
+ getChildren: (v1: Var<A>, v2: Var<B>, v3: Var<C>, v4: Var<D>, v5: Var<E>, v6: Var<F>) => Html
+}
+
+interface WithState7<A, B, C, D, E, F, G> {
+ type: 'WithState7'
+ init: [A, B, C, D, E, F, G]
+ getChildren: (v1: Var<A>, v2: Var<B>, v3: Var<C>, v4: Var<D>, v5: Var<E>, v6: Var<F>, v7: Var<G>) => Html
+}
+
+export interface Attributes {
+ [key: string]: Rx<AttributeValue> | AttributeValue
+}
+
+type AttributeValue
+ = undefined
+ | null
+ | string
+ | number
+ | boolean
+ | ((event: Event) => void)
+ | ((element: Element) => void)
+
+function isHtml(x: any): x is Html {
+ return (typeof x === 'string'
+ || typeof x === 'number'
+ || isTag(x)
+ || isWithState(x)
+ || isWithState2(x)
+ || isWithState3(x)
+ || isWithState4(x)
+ || isWithState5(x)
+ || isWithState6(x)
+ || isWithState7(x)
+ || isRx(x)
+ || Array.isArray(x))
+}
+
+type ValueOrArray<T> = T | Array<ValueOrArray<T>>
+
+export function s(
+ tagName: string,
+ x?: Attributes | Html,
+ ...children: Array<Html>
+): Tag {
+ return node(true, tagName, x, ...children)
+}
+
+export function h(
+ tagName: string,
+ x?: Attributes | Html,
+ ...children: Array<Html>
+): Tag {
+ return node(false, tagName, x, ...children)
+}
+
+export function node(
+ isSvg: boolean,
+ tagName: string,
+ x?: Attributes | Html,
+ ...children: Array<Html>
+): Tag {
+ if (x === undefined || x == null || x === false) {
+ return {
+ type: 'Tag',
+ tagName,
+ isSvg,
+ attributes: {}
+ }
+ } else if (isHtml(x)) {
+ return {
+ type: 'Tag',
+ tagName,
+ isSvg,
+ attributes: {},
+ children: [x, ...children],
+ }
+ } else {
+ let attributes = x as Attributes
+ let onmount, onunmount
+ if ('onmount' in attributes) {
+ onmount = attributes['onmount'] as (element: Element) => void
+ delete attributes['onmount']
+ }
+ if ('onunmount' in attributes) {
+ onunmount = attributes['onunmount'] as (element: Element) => void
+ delete attributes['onunmount']
+ }
+ return {
+ type: 'Tag',
+ tagName,
+ isSvg,
+ attributes,
+ children,
+ onmount,
+ onunmount
+ }
+ }
+}
+
+export function withState<A>(init: A, getChildren: (v: Var<A>) => Html): WithState<A> {
+ return {
+ type: 'WithState',
+ init,
+ getChildren
+ }
+}
+
+export function withState2<A, B>(init: [A, B], getChildren: (v1: Var<A>, v2: Var<B>) => Html): WithState2<A, B> {
+ return {
+ type: 'WithState2',
+ init,
+ getChildren
+ }
+}
+
+export function withState3<A, B, C>(init: [A, B, C], getChildren: (v1: Var<A>, v2: Var<B>, v3: Var<C>) => Html): WithState3<A, B, C> {
+ return {
+ type: 'WithState3',
+ init,
+ getChildren
+ }
+}
+
+export function withState4<A, B, C, D>(init: [A, B, C, D], getChildren: (v1: Var<A>, v2: Var<B>, v3: Var<C>, v4: Var<D>) => Html): WithState4<A, B, C, D> {
+ return {
+ type: 'WithState4',
+ init,
+ getChildren
+ }
+}
+
+export function withState5<A, B, C, D, E>(init: [A, B, C, D, E], getChildren: (v1: Var<A>, v2: Var<B>, v3: Var<C>, v4: Var<D>, v5: Var<E>) => Html): WithState5<A, B, C, D, E> {
+ return {
+ type: 'WithState5',
+ init,
+ getChildren
+ }
+}
+
+export function withState6<A, B, C, D, E, F>(init: [A, B, C, D, E, F], getChildren: (v1: Var<A>, v2: Var<B>, v3: Var<C>, v4: Var<D>, v5: Var<E>, v6: Var<F>) => Html): WithState6<A, B, C, D, E, F> {
+ return {
+ type: 'WithState6',
+ init,
+ getChildren
+ }
+}
+
+export function withState7<A, B, C, D, E, F, G>(init: [A, B, C, D, E, F, G], getChildren: (v1: Var<A>, v2: Var<B>, v3: Var<C>, v4: Var<D>, v5: Var<E>, v6: Var<F>, v7: Var<G>) => Html): WithState7<A, B, C, D, E, F, G> {
+ return {
+ type: 'WithState7',
+ init,
+ getChildren
+ }
+}
+
+// Rx
+
+export type RxAble<A> = Rx<A> | A
+
+export class Rx<A> {
+ map<B>(f: (value: A) => B): Rx<B> {
+ return new Map<A, B>(this, f)
+ }
+
+ flatMap<B>(f: (value: A) => Rx<B>): Rx<B> {
+ return new FlatMap<A, B>(this, f)
+ }
+}
+
+export function map2<A, B, C>(rx: [Rx<A>, Rx<B>], fn: (a: A, b: B) => C): Rx<C> {
+ return rx[0].flatMap(a => rx[1].map(b => fn(a, b)))
+}
+
+export function map3<A, B, C, D>(rx: [Rx<A>, Rx<B>, Rx<C>], fn: (a: A, b: B, c: C) => D): Rx<D> {
+ return rx[0].flatMap(a => rx[1].flatMap(b => rx[2].map(c => fn(a, b, c))))
+}
+
+export function map4<A, B, C, D, E>(rx: [Rx<A>, Rx<B>, Rx<C>, Rx<D>], fn: (a: A, b: B, c: C, d: D) => E): Rx<E> {
+ return rx[0].flatMap(a => rx[1].flatMap(b => rx[2].flatMap(c => rx[3].map(d => fn(a, b, c, d)))))
+}
+
+export function map5<A, B, C, D, E, F>(rx: [Rx<A>, Rx<B>, Rx<C>, Rx<D>, Rx<E>], fn: (a: A, b: B, c: C, d: D, e: E) => F): Rx<F> {
+ return rx[0].flatMap(a => rx[1].flatMap(b => rx[2].flatMap(c => rx[3].flatMap(d => rx[4].map(e => fn(a, b, c, d, e))))))
+}
+
+export function map6<A, B, C, D, E, F, G>(rx: [Rx<A>, Rx<B>, Rx<C>, Rx<D>, Rx<E>, Rx<F>], fn: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Rx<G> {
+ return rx[0].flatMap(a => rx[1].flatMap(b => rx[2].flatMap(c => rx[3].flatMap(d => rx[4].flatMap(e => rx[5].map(f => fn(a, b, c, d, e, f)))))))
+}
+
+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, update: (v: Var<A>) => ((f: ((value: A) => A)) => void)) {
+ super()
+ this.id = id
+ this.type = 'Var'
+ this.update = update(this)
+ }
+}
+
+class Map<A, B> extends Rx<B> {
+ readonly type: 'Map'
+ readonly rx: Rx<A>
+ readonly f: (value: A) => B
+
+ constructor(rx: Rx<A>, f: (value: A) => B) {
+ super()
+ this.type = 'Map'
+ this.rx = rx
+ this.f = f
+ }
+}
+
+class FlatMap<A, B> extends Rx<B> {
+ readonly type: 'FlatMap'
+ readonly rx: Rx<A>
+ readonly f: (value: A) => Rx<B>
+
+ constructor(rx: Rx<A>, f: (value: A) => Rx<B>) {
+ super()
+ this.type = 'FlatMap'
+ this.rx = rx
+ this.f = f
+ }
+}
+
+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(element: HTMLElement, html: Html): Cancelable {
+ const state = new State()
+ let appendRes = appendChild(state, element, html)
+ return appendRes.cancel
+}
+
+interface StateEntry<A> {
+ value: A
+ subscribers: Array<(value: A) => void>
+}
+
+class State {
+ readonly state: {[key: string]: StateEntry<any>}
+ varCounter: bigint
+
+ constructor() {
+ this.state = {}
+ this.varCounter = BigInt(0)
+ }
+
+ register<A>(initValue: A) {
+ const v = new Var(this.varCounter.toString(), v => (f => this.update(v, f)))
+ this.varCounter += BigInt(1)
+ this.state[v.id] = {
+ value: initValue,
+ subscribers: []
+ }
+ return v
+ }
+
+ unregister<A>(v: Var<A>) {
+ delete this.state[v.id]
+ }
+
+ get<A>(v: Var<A>) {
+ return this.state[v.id].value
+ }
+
+ update<A>(v: Var<A>, f: (value: A) => A) {
+ if (v.id in this.state) {
+ const value = f(this.state[v.id].value)
+ this.state[v.id].value = value
+ this.state[v.id].subscribers.forEach(notify => {
+ // Don’t notify if it has been removed from a precedent notifier
+ if (this.state[v.id].subscribers.indexOf(notify) !== -1) {
+ notify(value)
+ }
+ })
+ }
+ }
+
+ subscribe<A>(v: Var<A>, notify: (value: A) => void): Cancelable {
+ this.state[v.id].subscribers.push(notify)
+ return () => this.state[v.id].subscribers = this.state[v.id].subscribers.filter(n => n !== notify)
+ }
+}
+
+// Cancelable
+
+type Cancelable = () => void
+
+const voidCancel = () => {}
+
+// Removable
+
+type Removable = () => void
+
+const voidRemove = () => {}
+
+// Rx run
+
+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
+ } else if (isMap<A, any>(rx)) {
+ return rxRun(state, rx.rx, value => effect(rx.f(value)))
+ } else if (isFlatMap(rx)) {
+ let cancel1 = voidCancel
+ const cancel2 = rxRun(state, rx.rx, (value: A) => {
+ cancel1()
+ cancel1 = rxRun(state, rx.f(value), effect)
+ })
+ return () => {
+ 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 != null && 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> {
+ return x.type === 'Var'
+}
+
+function isMap<A, B>(x: any): x is Map<A, B> {
+ return x.type === 'Map'
+}
+
+function isFlatMap<A, B>(x: any): x is FlatMap<A, B> {
+ return x.type === 'FlatMap'
+}
+
+function isSequence<A>(x: any): x is Sequence<A> {
+ return x.type === 'Sequence'
+}
+
+// Append
+
+interface AppendResult {
+ cancel: Cancelable
+ remove: Removable
+ lastAdded?: Node
+}
+
+function appendChild(state: State, element: Element, child: Html, lastAdded?: Node): AppendResult {
+ if (Array.isArray(child)) {
+ let cancels: Array<Cancelable> = []
+ let removes: Array<Removable> = []
+ child.forEach((o) => {
+ const appendResult = appendChild(state, element, o, lastAdded)
+ cancels.push(appendResult.cancel)
+ removes.push(appendResult.remove)
+ lastAdded = appendResult.lastAdded
+ })
+ return {
+ cancel: () => cancels.forEach((o) => o()),
+ remove: () => removes.forEach((o) => o()),
+ lastAdded
+ }
+ } else if (typeof child == 'string') {
+ const node = document.createTextNode(child)
+ appendNode(element, node, lastAdded)
+ return {
+ cancel: voidCancel,
+ remove: () => element.removeChild(node),
+ lastAdded: node
+ }
+ } else if (typeof child == 'number') {
+ return appendChild(state, element, child.toString(), lastAdded)
+ } else if (isTag(child)) {
+ const { tagName, isSvg, attributes, children, onmount, onunmount } = child
+
+ const childElement = isSvg
+ ? document.createElementNS('http://www.w3.org/2000/svg', tagName)
+ : document.createElement(tagName)
+
+ const setAttr = isSvg
+ ? (key: any, value: any) => childElement.setAttribute(key, value)
+ // @ts-ignore
+ : (key: any, value: any) => childElement[key] = value
+
+ const cancelAttributes = Object.entries(attributes).map(([key, value]) => {
+ if (isRx<AttributeValue>(value)) {
+ return rxRun(state, value, newValue => setAttribute(setAttr, childElement, key, newValue))
+ } else {
+ setAttribute(setAttr, childElement, key, value)
+ }
+ })
+
+ const appendChildrenRes = appendChild(state, childElement, children)
+
+ appendNode(element, childElement, lastAdded)
+
+ if (onmount !== undefined) {
+ // Wait for the element to be on the page
+ window.setTimeout(() => onmount(childElement), 0)
+ }
+
+ return {
+ cancel: () => {
+ cancelAttributes.forEach(cancel => cancel !== undefined ? cancel() : {})
+ appendChildrenRes.cancel()
+ if (onunmount !== undefined) {
+ onunmount(childElement)
+ }
+ },
+ remove: () => element.removeChild(childElement),
+ lastAdded: childElement,
+ }
+ } else if (isWithState(child)) {
+ const { init, getChildren } = child
+ const v = state.register(init)
+ const children = getChildren(v)
+ const appendRes = appendChild(state, element, children)
+ return {
+ cancel: () => {
+ appendRes.cancel()
+ state.unregister(v)
+ },
+ remove: () => appendRes.remove(),
+ lastAdded: appendRes.lastAdded
+ }
+ } else if (isWithState2(child)) {
+ const { init, getChildren } = child
+ const [ init1, init2 ] = init
+ const v1 = state.register(init1)
+ const v2 = state.register(init2)
+ const children = getChildren(v1, v2)
+ const appendRes = appendChild(state, element, children)
+ return {
+ cancel: () => {
+ appendRes.cancel()
+ state.unregister(v1)
+ state.unregister(v2)
+ },
+ remove: () => appendRes.remove(),
+ lastAdded: appendRes.lastAdded
+ }
+ } else if (isWithState3(child)) {
+ const { init, getChildren } = child
+ const [ init1, init2, init3 ] = init
+ const v1 = state.register(init1)
+ const v2 = state.register(init2)
+ const v3 = state.register(init3)
+ const children = getChildren(v1, v2, v3)
+ const appendRes = appendChild(state, element, children)
+ return {
+ cancel: () => {
+ appendRes.cancel()
+ state.unregister(v1)
+ state.unregister(v2)
+ state.unregister(v3)
+ },
+ remove: () => appendRes.remove(),
+ lastAdded: appendRes.lastAdded
+ }
+ } else if (isWithState4(child)) {
+ const { init, getChildren } = child
+ const [ init1, init2, init3, init4 ] = init
+ const v1 = state.register(init1)
+ const v2 = state.register(init2)
+ const v3 = state.register(init3)
+ const v4 = state.register(init4)
+ const children = getChildren(v1, v2, v3, v4)
+ const appendRes = appendChild(state, element, children)
+ return {
+ cancel: () => {
+ appendRes.cancel()
+ state.unregister(v1)
+ state.unregister(v2)
+ state.unregister(v3)
+ state.unregister(v4)
+ },
+ remove: () => appendRes.remove(),
+ lastAdded: appendRes.lastAdded
+ }
+ } else if (isWithState5(child)) {
+ const { init, getChildren } = child
+ const [ init1, init2, init3, init4, init5 ] = init
+ const v1 = state.register(init1)
+ const v2 = state.register(init2)
+ const v3 = state.register(init3)
+ const v4 = state.register(init4)
+ const v5 = state.register(init5)
+ const children = getChildren(v1, v2, v3, v4, v5)
+ const appendRes = appendChild(state, element, children)
+ return {
+ cancel: () => {
+ appendRes.cancel()
+ state.unregister(v1)
+ state.unregister(v2)
+ state.unregister(v3)
+ state.unregister(v4)
+ state.unregister(v5)
+ },
+ remove: () => appendRes.remove(),
+ lastAdded: appendRes.lastAdded
+ }
+ } else if (isWithState6(child)) {
+ const { init, getChildren } = child
+ const [ init1, init2, init3, init4, init5, init6 ] = init
+ const v1 = state.register(init1)
+ const v2 = state.register(init2)
+ const v3 = state.register(init3)
+ const v4 = state.register(init4)
+ const v5 = state.register(init5)
+ const v6 = state.register(init6)
+ const children = getChildren(v1, v2, v3, v4, v5, v6)
+ const appendRes = appendChild(state, element, children)
+ return {
+ cancel: () => {
+ appendRes.cancel()
+ state.unregister(v1)
+ state.unregister(v2)
+ state.unregister(v3)
+ state.unregister(v4)
+ state.unregister(v5)
+ state.unregister(v6)
+ },
+ remove: () => appendRes.remove(),
+ lastAdded: appendRes.lastAdded
+ }
+ } else if (isWithState7(child)) {
+ const { init, getChildren } = child
+ const [ init1, init2, init3, init4, init5, init6, init7 ] = init
+ const v1 = state.register(init1)
+ const v2 = state.register(init2)
+ const v3 = state.register(init3)
+ const v4 = state.register(init4)
+ const v5 = state.register(init5)
+ const v6 = state.register(init6)
+ const v7 = state.register(init7)
+ const children = getChildren(v1, v2, v3, v4, v5, v6, v7)
+ const appendRes = appendChild(state, element, children)
+ return {
+ cancel: () => {
+ appendRes.cancel()
+ state.unregister(v1)
+ state.unregister(v2)
+ state.unregister(v3)
+ state.unregister(v4)
+ state.unregister(v5)
+ state.unregister(v6)
+ state.unregister(v7)
+ },
+ remove: () => appendRes.remove(),
+ lastAdded: appendRes.lastAdded
+ }
+ } else if (isRx(child)) {
+ const rxBase = document.createTextNode('')
+ appendNode(element, rxBase, lastAdded)
+ let appendRes: AppendResult = {
+ cancel: voidCancel,
+ remove: voidRemove,
+ lastAdded: rxBase
+ }
+ const cancelRx = rxRun(state, child, (value: Html) => {
+ appendRes.cancel()
+ appendRes.remove()
+ appendRes = appendChild(state, element, value, rxBase)
+ })
+ return {
+ cancel: () => {
+ appendRes.cancel()
+ cancelRx()
+ },
+ remove: () => {
+ appendRes.remove()
+ element.removeChild(rxBase)
+ },
+ lastAdded: appendRes.lastAdded,
+ }
+ } else if (!child) {
+ return {
+ cancel: voidCancel,
+ remove: voidRemove,
+ lastAdded
+ }
+ } else {
+ throw new Error(`Unrecognized child: ${child}`)
+ }
+}
+
+function isTag<A>(x: any): x is Tag {
+ return x != null && x.type === 'Tag'
+}
+
+function isWithState<A>(x: any): x is WithState<A> {
+ return x != null && x.type === 'WithState'
+}
+
+function isWithState2<A, B>(x: any): x is WithState2<A, B> {
+ return x != null && x.type === 'WithState2'
+}
+
+function isWithState3<A, B, C>(x: any): x is WithState3<A, B, C> {
+ return x != null && x.type === 'WithState3'
+}
+
+function isWithState4<A, B, C, D>(x: any): x is WithState4<A, B, C, D> {
+ return x != null && x.type === 'WithState4'
+}
+
+function isWithState5<A, B, C, D, E>(x: any): x is WithState5<A, B, C, D, E> {
+ return x != null && x.type === 'WithState5'
+}
+
+function isWithState6<A, B, C, D, E, F>(x: any): x is WithState6<A, B, C, D, E, F> {
+ return x != null && x.type === 'WithState6'
+}
+
+function isWithState7<A, B, C, D, E, F, G>(x: any): x is WithState7<A, B, C, D, E, F, G> {
+ return x != null && x.type === 'WithState7'
+}
+
+function appendNode(base: Element, node: Node, lastAdded?: Node) {
+ if (lastAdded !== undefined) {
+ base.insertBefore(node, lastAdded.nextSibling)
+ } else {
+ base.append(node)
+ }
+}
+
+function setAttribute(setAttr: (key: any, value: any) => void, element: Element, key: string, attribute: AttributeValue) {
+ if (attribute === undefined) {
+ // Do nothing
+ } else if (attribute === true) {
+ setAttr(key, 'true')
+ } else if (attribute === false) {
+ // @ts-ignore
+ if (key in element) setAttr(key, false)
+ } else if (typeof attribute === 'number') {
+ setAttr(key, attribute.toString())
+ } else if (typeof attribute === 'string') {
+ setAttr(key, attribute)
+ } else {
+ // @ts-ignore
+ setAttr(key, (event: Event) => attribute(event))
+ }
+}