@motorcycle/mostly-dom
v5.0.0
Published
Motorcycle.ts adapter for mostly-dom. Built on @motorcycle/dom.
Downloads
16
Readme
@motorcycle/mostly-dom -- 4.1.0
Motorcycle.ts adapter for mostly-dom. Built on @motorcycle/dom.
Get it
yarn add @motorcycle/mostly-dom
# or
npm install --save @motorcycle/mostly-dom
API Documentation
All functions are curried!
DomSinks
Sinks type returns by a DOM component.
export type DomSinks = { readonly view$: Stream<VNode> }
DomSource
DomSource type as defined by @motorcycle/dom
interface DomSource {
query(cssSelector: CssSelector): DomSource
elements<El extends Element = Element>(): Stream<ReadonlyArray<El>>
events<Ev extends Event = Event>(eventType: StandardEvents, options?: EventListenerOptions): Stream<Ev>
cssSelectors(): ReadonlyArray<CssSelector>
}
DomSources
Sources type expected by a DOM component.
export type DomSources<A = Element, B = Event> = { readonly dom: DomSource<A, B> }
Types
Virtual DOM node type from mostly-dom
// All other types are used directly from mostly-dom
// https://github.com/TylorS167/mostly-dom
hyperscript-helpers
Functions for describing your views.
Re-exported from mostly-dom
import { VNode, div, h1, button } from '@motorcycle/mostly-dom'
function view(amount: number): VNode {
return div([
h1(`Clicked ${amount} times!`),
button('Click me')
])
}
export * from 'mostly-dom'
export * from './isolate'
export * from './makeDomComponent'
isolate<Sources extends DomSources, Sinks extends DomSinks>(component: Component<Sources, Sinks>, key: string, sources: Sources): Sinks
Isolates a component by adding an isolation class name to the outermost DOM element emitted by the component’s view stream.
The isolation class name is generated by appending the given isolation key
to the prefix $$isolation$$-
, e.g., given foo
as key
produces
$$isolation$$-foo
.
Isolating components are useful especially when dealing with lists of a specific component, so that events can be differentiated between the siblings. However, isolated components are not isolated from access by an ancestor DOM element.
Note that isolate
is curried.
import { empty } from '@motorcycle/stream'
import { createDomSource } from '@motorcycle/dom'
const sources = createDomSource(empty())
const sinks = isolate(MyComponent, `myIsolationKey`, sources)
export const isolate: IsolatedComponent = curry3(function isolate<
Sources extends DomSources,
Sinks extends DomSinks
>(component: Component<Sources, Sinks>, key: string, sources: Sources): Sinks {
const { dom } = sources
const isolatedDom = dom.query(`.${KEY_PREFIX}${key}`)
const sinks = component(Object.assign({}, sources, { dom: isolatedDom }))
const isolatedSinks = Object.assign({}, sinks, { view$: isolateView(sinks.view$, key) })
return isolatedSinks
})
const KEY_PREFIX = `__isolation__`
function isolateView(view$: Stream<VNode>, key: string) {
const prefixedKey = KEY_PREFIX + key
return tap(vNode => {
const { props: { className: className = EMPTY_CLASS_NAME } } = vNode
const needsIsolation = className.indexOf(prefixedKey) === -1
if (needsIsolation)
vNode.props.className = removeSuperfluousSpaces(
join(CLASS_NAME_SEPARATOR, [className, prefixedKey])
)
}, view$)
}
const EMPTY_CLASS_NAME = ``
const CLASS_NAME_SEPARATOR = ` `
function removeSuperfluousSpaces(str: string): string {
return str.replace(RE_TWO_OR_MORE_SPACES, CLASS_NAME_SEPARATOR)
}
const RE_TWO_OR_MORE_SPACES = /\s{2,}/g
export interface IsolatedComponent {
<Sources extends DomSources, Sinks extends DomSinks>(
component: Component<Sources, Sinks>,
key: string,
sources: Sources
): Sinks
<Sources extends DomSources, Sinks extends DomSinks>(
component: Component<Sources, Sinks>,
key: string
): Component<Sources, Sinks>
<Sources extends DomSources, Sinks extends DomSinks>(
component: Component<Sources, Sinks>
): IsolatedComponentArity2<Sources, Sinks>
}
export interface IsolatedComponentArity2<Sources extends DomSources, Sinks extends DomSinks> {
(key: string, sources: Sources): Sinks
(key: string): Component<Sources, Sinks>
}
makeDomComponent(element: Element): (sinks: DomSinks) => DomSources
Takes an element and returns a DOM component function.
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 }
}
}