@motorcycle/mostly-dom
Version:
Motorcycle.ts adapter for mostly-dom. Built on @motorcycle/dom.
89 lines (79 loc) • 2.33 kB
text/typescript
import { DomSource, createDomSource } from '@motorcycle/dom'
import { ElementVNode, VNode, elementToVNode, init } from 'mostly-dom'
import { IOComponent, Stream } from '@motorcycle/types'
import { drain, hold, map, scan } from '@motorcycle/stream'
import { prop } from '@typed/prelude'
import { vNodeWrapper } from './vNodeWrapper'
/**
* Sources type expected by a DOM component.
* @name DomSources
* @example
* export type DomSources<A = Element, B = Event> = { readonly dom: DomSource<A, B> }
* @type
*/
export type DomSources<A = Element, B = Event> = { readonly dom: DomSource<A, B> }
/**
* Sinks type returns by a DOM component.
* @name DomSinks
* @example
* export type DomSinks = { readonly view$: Stream<VNode> }
* @type
*/
export type DomSinks = { readonly view$: Stream<VNode> }
const toElement = map(prop<ElementVNode, 'element'>('element'))
/**
* Takes an element and returns a DOM component function.
*
* @name makeDomComponent(element: Element): (sinks: DomSinks) => DomSources
* @example
* import {
* makeDomComponent,
* DomSources,
* DomSinks,
* VNode,
* events,
* query,
* div,
* h1,
* button
* } from '@motorcycle/mostly-dom'
* import { run } from '@motorcycle/run'
*
* const element = document.querySelector('#app')
*
* if (!element) throw new Error('unable to find element')
*
* run(Main, makeDomComponent(element))
*
* function Main(sources: DomSources): DomSinks {
* const { dom } = sources
*
* const click$: Stream<Event> = events('click', query('button'))
*
* const amount$: Stream<number> = scan(x => x + 1, 0, click$)
*
* const view$: Stream<VNode> = map(view, amount$)
*
* return { view$ }
* }
*
* function view(amount: number) {
* return div([
* h1(`Clicked ${amount} times`),
* button(`Click me`)
* ])
* }
*/
export function makeDomComponent(element: Element): IOComponent<DomSinks, DomSources> {
const rootVNode = elementToVNode(element)
const wrapVNode = map(vNodeWrapper(element))
const patch = scan(init(), rootVNode)
return function Dom(sinks: DomSinks): DomSources {
const { view$ } = sinks
const elementVNode$ = patch(wrapVNode(view$))
const element$ = hold(toElement(elementVNode$))
const dom = createDomSource(element$)
drain(element$)
return { dom }
}
}