@motorcycle/dom

Declarative, functional, reactive abstractions for the DOM

Usage no npm install needed!

<script type="module">
  import motorcycleDom from 'https://cdn.skypack.dev/@motorcycle/dom';
</script>

README

@motorcycle/dom -- 16.1.0

Declarative, functional, reactive abstractions for the DOM

Get it

yarn add @motorcycle/dom
# or
npm install --save @motorcycle/dom

API Documentation

All functions are curried!

DomSource

A DOM source interface for objects to declaratively query the 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>
}

DomSource.cssSelectors(): ReadonlyArray<CssSelector>

Retrieves a list of all previously queried CSS selectors.

See an example
const queriedDomSource = domSource.query(`.myCssSelector`)
const cssSelectors = queriedDomSource.cssSelectors()

console.log(cssSelectors[0]) // .myCssSelector
See the code

cssSelectors(): ReadonlyArray<CssSelector>
}

export type CssSelector = string


DomSource.elements<El extends Element = Element>(): Stream<ReadonlyArray<El>>

Retrieves a stream of a list of elements matching previous queries.

NOTE: Elements will emit every single time the DOM is updated.

See an example
const queriedDomSource = domSource.query(`.myCssSelector`)
const elements$ = queriedDomSource.elements()
See the code

elements(): Stream<ReadonlyArray<A>>


DomSource.events<Ev extends Event = Event>(eventType: StandardEvents, options?: EventListenerOptions): Stream<Ev>

Retrieves a stream of events from elements matching previous queries.

DomSource.events optionally takes a second parameter of EventListernerOptions, which specifies whether event listeners will listen to events during the capturing phase. If not provided, all event listeners will use bubbling phase.

See an example
const queriedDomSource = domSource.query(`.myCssSelector`)
const clickEvent$: Stream<MouseEvent> = queriedDomSource.events<MouseEvent>('click')
See the code

events<Ev extends B = B>(eventType: StandardEvents, options?: EventListenerOptions): Stream<Ev>


DomSource.query(cssSelector: CssSelector): DomSource

Queries for elements and events for a specified CSS selector.

See an example
const queriedDomSource = domSource.query(`.myCssSelector`)
See the code

query<C extends A = A>(cssSelector: CssSelector): DomSource<C, B>


HistoryEffect


export type HistoryEffect = (history: History) => void

HistorySinks


export type HistorySinks = {
  readonly history$: Stream<HistoryEffect>
}

HistorySources


export type HistorySources<State = any> = {
  readonly location$: Stream<Readonly<Location>>
  readonly state$: Stream<State>
}

StandardEvents

Standard event types defined by MDN. All browser should have these implemented the same.


export type StandardEvents =
// name -    Event Types
 | 'abort' // UIEvent, ProgressEvent, Event
 | 'afterprint' // Event;
 | 'animationend' // AnimationEvent
 | 'animationiteration' // AnimationEvent
 | 'animationstart' // AnimationEvent
 | 'audioprocess' // AudioProcessingEvent
 | 'audioend' // Event
 | 'audiostart' // Event
 | 'beforprint' // Event
 | 'beforeunload' // BeforeUnloadEvent
 | 'beginEvent' // TimeEvent
 | 'blocked' // Event
 | 'blur' // FocusEvent
 | 'boundary' // SpeechsynthesisEvent
 | 'cached' // Event
 | 'canplay' // Event
 | 'canplaythrough' // Event
 | 'change' // Event
 | 'chargingchange' // Event
 | 'chargingtimechange' // Event
 | 'checking' // Event
 | 'click' // MouseEvent
 | 'close' // Event
 | 'complete' // Event, OfflineAudioCompletionEvent
 | 'compositionend' // CompositionEvent
 | 'compositionstart' // CompositionEvent
 | 'compositionupdate' // CompositionEvent
 | 'contextmenu' // MoustEvent
 | 'copy' // ClipboardEvent
 | 'cut' // ClipboardEvent
 | 'dblclick' // MouseEvent
 | 'devicechange' // Event
 | 'devicelight' // DeviceLightEvent
 | 'devicemotion' // DeviceMotionEvent
 | 'deviceorientation' // DeviceOrientationEvent
 | 'deviceproximity' // DeviceProximityEvent
 | 'dischargingtimechange' // Event
 | 'DOMActivate' // UIEvent
 | 'DOMAttributeNameChanged' // MutationNameEvent
 | 'DOMAttrModified' // Mutationevent
 | 'DOMCharacterDataModified' // MutationEvent
 | 'DOMContentLoaded' // Event
 | 'DOMElementNamedChanged' // MutationNameEvent
 | 'DOMNodeInserted' // MutationEvent
 | 'DOMNodeInsertedIntoDocument' // MutationEvent
 | 'DOMNodeRemoved' // MutationEvent
 | 'DOMNodeRemovedFromDocument' // MutationEvent
 | 'DOMSubtreeModified' // MutationEvent
 | 'downloaded' // Event
 | 'drag' // DragEvent
 | 'dragend' // DragEvent
 | 'dragenter' // DragEvent
 | 'dragleave' // DragEvent
 | 'dragover' // DragEvent
 | 'dragstart' // DragEvent
 | 'drop' // DragEvent
 | 'durationchange' // Event
 | 'emptied' // Event
 | 'end' // Event, SpeechSynthesisEvent
 | 'ended' // Event
 | 'endEvent' // TimeEvent
 | 'error' // UIEvent | ProgressEvent | Event
 | 'focus' // FocusEvent
 | 'fullscreenchange' // Event
 | 'fullscreenerror' // Event
 | 'gamepadconnected' // GamepadEvent
 | 'gamepaddisconnected' // GamepadEvent
 | 'gotpointercapture' // PointerEvent
 | 'hashchange' // HashChangEvent
 | 'lostpointercapture' // PointerEvent
 | 'input' // event
 | 'invalid' // Event
 | 'keydown' // KeyboardEvent
 | 'keypress' // KeyboardEvent
 | 'keyup' // KeyboardEvent
 | 'languagechange' // Event
 | 'levelchange' // Event
 | 'load' // UIEvent, ProgressEvent
 | 'loadeddata' // Event
 | 'loadedmetadata' // Event
 | 'loadend' // ProgressEvent
 | 'loadstart' // ProgressEvent
 | 'mark' // SpeechSynthesisEvent
 | 'message' // MessageEvent, ServiceWorkerMessageEvent, ExtendableMessageEvent
 | 'mousedown' // MouseEvent
 | 'mouseenter' // MouseEvent
 | 'mouseleave' // MouseEvent
 | 'mousemove' // MouseEvent
 | 'mouseout' // MouseEvent
 | 'mouseover' // Mouseevent
 | 'nomatch' // SpeechRecognitionEvent
 | 'notificationclick' // NotificationEvent
 | 'noupdate' // event
 | 'obsolete' // Event
 | 'offline' // event
 | 'online' // Event
 | 'open' // event
 | 'orientationchange' // Event
 | 'pagehide' // PageTransitionEvent
 | 'pageshow' // PageTransitionEvent
 | 'paste' // ClipboardEvent
 | 'pause' // Event, SpeechSynthesisEvent
 | 'pointercancel' // PointerEvent
 | 'pointerdown' // PointerEvent
 | 'pointerenter' // PointerEvent
 | 'pointerleave' // PointerEvent
 | 'pointerlockchange' // Event
 | 'pointerlockerror' // Event
 | 'pointermove' // PointerEvent
 | 'pointerout' // PointerEvent
 | 'pointerover' // PointerEvent
 | 'pointerup' // PointerEvent
 | 'play' // Event
 | 'playing' // Event
 | 'popstate' // PopStateEvent
 | 'progress' // ProgressEvent
 | 'push' // PushEvent
 | 'pushsubscriptionchange' // PushEvent
 | 'ratechange' // Event
 | 'readystatechange' // Event
 | 'repeatEvent' // TimeEvent
 | 'reset' // Event
 | 'resize' // UIEvent
 | 'resourcetimingbufferfull' // Performance
 | 'result' // SpeechRecognitionEvent
 | 'resume' // SpeechSynthesisEvent
 | 'scroll' // UIEvent
 | 'seeked' // Event
 | 'seeking' // Event
 | 'select' // UIEvent
 | 'selectstart' // UIEvent
 | 'selectionchange' // Event
 | 'show' // MouseEvent
 | 'soundend' // Event
 | 'soundstart' // Event
 | 'speechend' // Event
 | 'speechstart' // Event
 | 'stalled' // Event
 | 'start' // SpeechSynthesisEvent
 | 'storage' // StorageEvent
 | 'submit' // Event
 | 'success' // Event
 | 'suspend' // Event
 | 'SVGAbort' // SvgEvent
 | 'SVGError' // SvgEvent
 | 'SVGLoad' // SvgEvent
 | 'SVGResize' // SvgEvent
 | 'SVGScroll' // SvgEvent
 | 'SVGUnload' // SvgEvent
 | 'SVGZoom' // SvgEvent
 | 'timeout' // ProgressEvent
 | 'timeupdate' // Event
 | 'touchcancel' // TouchEvent
 | 'touchend' // TouchEvent
 | 'touchenter' // TouchEvent
 | 'touchleave' // TouchEvent
 | 'touchmove' // TouchEvent
 | 'touchstart' // TouchEvent ;
 | 'transitionend' // Transitionevent
 | 'unload' // UIEvent
 | 'updateready' // Event
 | 'upgradeneeded' // Event
 | 'userproximity' // UserProximityEvent
 | 'voiceschanged' // Event
 | 'versionchange' // Event
 | 'visibilitychange' // Event
 | 'volumechange' // Event
 | 'vrdisplayconnected' // Event
 | 'vrdisplaydisconnected' // Event
 | 'vrdisplaypresentchange' // Event
 | 'waiting' // Event
 | 'wheel' // WheelEvent

createDocumentDomSource(document$: Stream<Document>): DocumentDomSource

Takes in Stream\<Document\> and produces a DocumentDomSource. Stream\<Document\> is required and allows for the developer to decide which events cause the stream to emit.

See an example
import { createDocumentDomSource } from '@motorcycle/dom'
import { makeDomComponent } from '@motorcycle/mostly-dom'
import { constant } from '@motorcycle/stream'
import { UI } from './UI'

const element = document.querySelector('#app-container') as Element

const Dom = makeDomComponent(element)

function Effects(sinks) {
  const { view$ } = sinks
  
  const document$ = constant(document, view$)

  const { dom } = Dom({ view$ })
  
  return {
    dom,
    document: createDocumentDomSource(document$)
  }
}
See the code

export function createDocumentDomSource(document$: Stream<Document>): DomSource<Document, Event> {
  return new DocumentDomSource(document$)
}


createDomSource(element$: Stream<Element>): DomSource

Takes a stream of DOM Elements an returns a DomSource. This DomSource makes use of event delegation.

See an example
import { createDomSource } from '@motorcycle/dom'

const dom = createDomSource(element$)
See the code

export function createDomSource(element$: Stream<Element>): DomSource {
  return new EventDelegationDomSource(element$, [])
}


createWindowDomSource(window$: Stream<Window>): WindowDomSource

Takes in Stream\<Window\> and produces a WindowDomSource. Stream\<Document\> is required and allows for the developer to decide which events cause the stream to emit.

See an example
import { createWindowDomSource } from '@motorcycle/dom'
import { makeDomComponent } from '@motorcycle/mostly-dom'
import { constant } from '@motorcycle/stream'
import { UI } from './UI'

const element = document.querySelector('#app-container') as Element

const Dom = makeDomComponent(element)

function Effects(sinks) {
  const { view$ } = sinks

  const window$ = constant(window, view$)

  const { dom } = Dom({ view$ })

  return {
    dom,
    window: createWindowDomSource(window$)
  }
}
See the code

export class WindowDomSource implements DomSource<Window, Event> {
  public window$: Stream<Window>

  constructor(window$: Stream<Window>) {
    this.window$ = window$
  }

  public query(): DomSource<Window, Event> {
    return this
  }

  public elements(): Stream<ReadonlyArray<Window>> {
    return map(Array, this.window$)
  }

  public events<Ev extends Event = Event>(
    eventType: StandardEvents,
    options: EventListenerOptions = {}
  ): Stream<Ev> {
    const { window$ } = this

    const event$ = map(window => new EventStream(eventType, window, options), window$)

    return multicast(switchLatest(event$))
  }

  public cssSelectors(): ReadonlyArray<CssSelector> {
    return WINDOW_CSS_SELECTORS
  }
}


elements<A = Element, B = Event>>(dom: DomSource<A, B>): Stream<ReadonlyArray<A>>

Takes a DomSource and returns a stream of Array of elements matches previous queries.

See an example
import { DomSource, elements } from '@motorcycle/dom'

type Sources = { dom: DomSource } 

function Component(sources: Sources) {
  const { dom } = sources

  const elements$ = elements(dom)

  ...
}
See the code

export function elements<A = Element, B = Event>(dom: DomSource<A, B>): Stream<ReadonlyArray<A>> {
  return dom.elements()
}


event<A = Element, B = Event>>(type: StandardEvents, dom: DomSource<A, B>): Stream<B>

Takes an event type and a DomSource and returns a stream of events.

See an example
import { events } from '@motorcycle/dom'

const click$ = events('click', dom)
See the code

export const events: Events = curry2(function<A = Element, B = Event>(
  eventType: StandardEvents,
  dom: DomSource<A, B>
): Stream<B> {
  return dom.events<B>(eventType)
})

export interface Events {
  <A = Element, B = Event>(eventType: StandardEvents, dom: DomSource<A, B>): Stream<B>
  <A = Element, B = Event>(eventType: StandardEvents): (dom: DomSource<A, B>) => Stream<B>
  (eventType: StandardEvents): <A = Element, B = Event>(dom: DomSource<A, B>) => Stream<B>
}

export const abortEvent = events<Element, UIEvent | ProgressEvent | Event>('abort')
export const afterPrintEvent = events<Element, Event>('afterprint')
export const animationEndEvent = events<Element, AnimationEvent>('animationend')
export const animationIterationEvent = events<Element, AnimationEvent>('animationiteration')
export const animationStartEvent = events<Element, AnimationEvent>('animationstart')
export const audioProcessEvent = events<Element, AudioProcessingEvent>('audioprocess')
export const audioEndEvent = events<Element, Event>('audioend')
export const audioStartEvent = events<Element, Event>('audiostart')
export const beforeUnloadEvent = events<Element, BeforeUnloadEvent>('beforeunload')
export const beginEvent = events<Element, Event>('beginEvent')
export const blockedEvent = events<Element, Event>('blocked')
export const blurEvent = events<Element, FocusEvent>('blur')
export const boundaryEvent = events<Element, SpeechSynthesisEvent>('boundary')
export const cachedEvent = events<Element, Event>('cached')
export const canPlayThroughEvent = events<Element, Event>('canplaythrough')
export const changeEvent = events<Element, Event>('change')
export const chargingChangeEvent = events<Element, Event>('chargingchange')
export const chargingTimeChangeEvent = events<Element, Event>('chargingtimechange')
export const checkingEvent = events<Element, Event>('checking')
export const clickEvent = events<Element, MouseEvent>('click')
export const closeEvent = events<Element, Event>('close')
export const completeEvent = events<Element, OfflineAudioCompletionEvent | Event>('complete')
export const compositionEndEvent = events<Element, CompositionEvent>('compositionend')
export const compositionStartEvent = events<Element, CompositionEvent>('compositionstart')
export const compositionUpdateEvent = events<Element, CompositionEvent>('compositionupdate')
export const contextMenuEvent = events<Element, MouseEvent>('contextmenu')
export const copyEvent = events<Element, ClipboardEvent>('copy')
export const cutEvent = events<Element, ClipboardEvent>('cut')
export const dblclickEvent = events<Element, MouseEvent>('dblclick')
export const deviceChangeEvent = events<Element, Event>('devicechange')
export const deviceLightEvent = events<Element, DeviceLightEvent>('devicelight')
export const deviceMotionEvent = events<Element, DeviceMotionEvent>('devicemotion')
export const deviceOrientationEvent = events<Element, DeviceOrientationEvent>('deviceorientation')
export const deviceProximityEvent = events<Element, DeviceMotionEvent>('deviceproximity')
export const dischargingTimeChangeEvent = events<Element, Event>('dischargingtimechange')
export const downloadedEvent = events<Element, Event>('downloaded')
export const dragEvent = events<Element, DragEvent>('drag')
export const dragEndEvent = events<Element, DragEvent>('dragend')
export const dragEnterEvent = events<Element, DragEvent>('dragenter')
export const dragLeaveEvent = events<Element, DragEvent>('dragleave')
export const dragOverEvent = events<Element, DragEvent>('dragover')
export const dragstartEvent = events<Element, DragEvent>('dragstart')
export const dropEvent = events<Element, DragEvent>('drop')
export const durationChangeEvent = events<Element, Event>('durationchange')
export const emptiedEvent = events<Element, Event>('emptied')
export const endEvent = events<Element, Event | SpeechSynthesisEvent>('end')
export const endedEvent = events<Element, Event>('ended')
export const focusEvent = events<Element, FocusEvent>('focus')
export const fullScreenChangeEvent = events<Element, Event>('fullscreenchange')
export const fullScreenErrorEvent = events<Element, Event>('fullscreenerror')
export const gamepadConnectedEvent = events<Element, GamepadEvent>('gamepadconnected')
export const gamepadDisconnectedEvent = events<Element, GamepadEvent>('gamepaddisconnected')
export const hashChangeEvent = events<Element, HashChangeEvent>('hashchange')
export const lostPointerCaptureEvent = events<Element, PointerEvent>('lostpointercapture')
export const inputEvent = events<Element, Event>('input')
export const invalidEvent = events<Element, Event>('invalid')
export const keyDownEvent = events<Element, KeyboardEvent>('keydown')
export const keyPressEvent = events<Element, KeyboardEvent>('keypress')
export const keyUpEvent = events<Element, KeyboardEvent>('keyup')
export const languageChangeEvent = events<Element, Event>('languagechange')
export const levelChangeEvent = events<Element, Event>('levelchange')
export const loadEvent = events<Element, UIEvent | ProgressEvent>('load')
export const loadedDataEvent = events<Element, Event>('loadeddata')
export const loadedMetadataEvent = events<Element, Event>('loadedmetadata')
export const loadEndEvent = events<Element, ProgressEvent>('loadend')
export const loadStartEvent = events<Element, ProgressEvent>('loadstart')
export const markEvent = events<Element, SpeechSynthesisEvent>('mark')
export const messageEvent = events<Element, MessageEvent | ServiceWorkerMessageEvent>('message')
export const mouseDownEvent = events<Element, MouseEvent>('mousedown')
export const mouseEnterEvent = events<Element, MouseEvent>('mouseenter')
export const mouseLeaveEvent = events<Element, MouseEvent>('mouseleave')
export const mouseMoveEvent = events<Element, MouseEvent>('mousemove')
export const mouseOutEvent = events<Element, MouseEvent>('mouseout')
export const mouseOverEvent = events<Element, MouseEvent>('mouseover')
export const noMatchEvent = events<Element, SpeechSynthesisEvent>('nomatch')
export const notificationClickEvent = events<Element, Event>('notificationclick')
export const noUpdateEvent = events<Element, Event>('noupdate')
export const obsoleteEvent = events<Element, Event>('obsolete')
export const offlineEvent = events<Element, Event>('offline')
export const onlineEvent = events<Element, Event>('online')
export const openEvent = events<Element, Event>('open')
export const orientationChangeEvent = events<Element, Event>('orientationchange')
export const pageShowEvent = events<Element, PageTransitionEvent>('pageshow')
export const pasteEvent = events<Element, ClipboardEvent>('paste')
export const pauseEvent = events<Element, Event | SpeechSynthesisEvent>('pause')
export const pointerCancelEvent = events<Element, PointerEvent>('pointercancel')
export const pointerDownEvent = events<Element, PointerEvent>('pointerdown')
export const pointerLockChangeEvent = events<Element, Event>('pointerlockchange')
export const pointerLockErrorEvent = events<Element, Event>('pointerlockerror')
export const pointerMoveEvent = events<Element, PointerEvent>('pointermove')
export const pointerOutEvent = events<Element, PointerEvent>('pointerout')
export const pointerOverEvent = events<Element, PointerEvent>('pointerover')
export const pointerUpEvent = events<Element, PointerEvent>('pointerup')
export const playEvent = events<Element, Event>('play')
export const playingEvent = events<Element, Event>('playing')
export const popStateEvent = events<Element, PopStateEvent>('popstate')
export const progressEvent = events<Element, ProgressEvent>('progress')
export const pushEvent = events<Element, Event>('push')
export const pushSubscriptionChangeEvent = events<Element, Event>('pushsubscriptionchange')
export const rateChangeEvent = events<Element, Event>('ratechange')
export const readyStateChangeEvent = events<Element, Event>('readystatechange')
export const resetEvent = events<Element, Event>('reset')
export const resizeEvent = events<Element, UIEvent>('resize')
export const resourceTimingBufferFullEvent = events<Element, Event>('resourcetimingbufferfull')
export const resultEvent = events<Element, Event>('result')
export const resumeEvent = events<Element, SpeechSynthesisEvent>('resume')
export const scrollEvent = events<Element, UIEvent>('scroll')
export const seekedEvent = events<Element, Event>('seeked')
export const seekingEvent = events<Element, Event>('seeking')
export const selectEvent = events<Element, UIEvent>('select')
export const selectStartEvent = events<Element, UIEvent>('selectstart')
export const selectionChangeEvent = events<Element, Event>('selectionchange')
export const showEvent = events<Element, MouseEvent>('show')
export const soundEndEvent = events<Element, Event>('soundend')
export const soundStartEvent = events<Element, Event>('soundstart')
export const speechEndEvent = events<Element, Event>('speechend')
export const stalledEvent = events<Element, Event>('stalled')
export const startEvent = events<Element, SpeechSynthesisEvent>('start')
export const storageEvent = events<Element, StorageEvent>('storage')
export const submitEvent = events<Element, Event>('submit')
export const successEvent = events<Element, Event>('success')
export const suspendEvent = events<Element, Event>('suspend')
export const timeoutEvent = events<Element, ProgressEvent>('timeout')
export const timeUpdateEvent = events<Element, Event>('timeupdate')
export const touchCancelEvent = events<Element, TouchEvent>('touchcancel')
export const touchEndEvent = events<Element, TouchEvent>('touchend')
export const touchEnterEvent = events<Element, TouchEvent>('touchenter')
export const touchLeaveEvent = events<Element, TouchEvent>('touchleave')
export const touchMoveEvent = events<Element, TouchEvent>('touchmove')
export const touchStartEvent = events<Element, TouchEvent>('touchstart')
export const transitionEndEvent = events<Element, TransitionEvent>('transitionend')
export const unloadEvent = events<Element, UIEvent>('unload')
export const updateReadyEvent = events<Element, Event>('updateready')
export const upgradeNeededEvent = events<Element, Event>('upgradeneeded')
export const userProximityEvent = events<Element, Event>('userproximity')
export const voicesChangedEvent = events<Element, Event>('voiceschanged')
export const versionChangeEvent = events<Element, Event>('versionchange')
export const visibilityChangeEvent = events<Element, Event>('visibilitychange')
export const volumeChangeEvent = events<Element, Event>('volumechange')
export const vrDisplayConnectedEvent = events<Element, Event>('vrdisplayconnected')
export const vrDisplayDisconnectedEvent = events<Element, Event>('vrdisplaydisconnected')
export const waitingEvent = events<Element, Event>('waiting')
export const wheelEvent = events<Element, WheelEvent>('wheel')


makeHistoryComponent<State = any>(location: Location, history: History): IOComponent<HistorySinks, HistorySources<State>>

Given implementations of the Location and History interfaces, it returns an IOComponent function which facilitates performing side-effects with the history API.

See an example
import { run } from '@motorcycle/run'
import { makeDomComponent, makeHistoryComponent } from '@motorcycle/dom'
import { UI } from './ui'

const rootElementSelector = '#app'
const element = document.querySelector(rootElementSelector)

if (!element) throw new Error(`Unable to find element by '${rootElementSelector}'`)

const Dom = makeDomComponent(element)
const History = makeHistoryComponent(location, history)

function IO(sinks) {
  return {
    ...Dom(sinks),
    ...History(sinks),
  }
}

run(UI, IO)
See the code

export function makeHistoryComponent<State = any>(
  location: Location,
  history: History
): IOComponent<HistorySinks, HistorySources<State>> {
  const popState$: Stream<PopStateEvent> =
    typeof window === void 0 ? empty() : new EventStream('popstate', window, {})

  return function History(sinks: HistorySinks): HistorySources {
    const { history$ } = sinks

    const performHistoryEffect$ = multicast(tap(historyEffect => historyEffect(history), history$))
    const updateSignal$ = multicast(merge<any>(performHistoryEffect$, popState$))

    const location$ = hold(startWith(location, constant(location, updateSignal$)))
    const state$ = hold(
      map(({ state }) => state, startWith(history, constant(history, updateSignal$)))
    )

    drain(performHistoryEffect$)

    return { location$, state$ }
  }
}


query<A, B, C>(cssSelector: CssSelector, domSource: DomSource<A, B>): DomSource<C, B>

A curried function for building more specific queries for elements.

See an example
import { DomSource, query, events } from '@motorcycle/dom'

type Sources = { dom: DomSource }

function Component(sources: Sources) {
  const { dom } = sources

  const button: DomSource = query('button', dom)
  const event$ = events('click', button)

  ...
}
See the code

export const query: Query = curry2(function queryWrapper<A = Element, B = Event, C extends A = A>(
  cssSelector: CssSelector,
  domSource: DomSource<A, B>
): DomSource<C, B> {
  return domSource.query<C>(cssSelector)
})

export interface Query {
  <A = Element, B = Event, C = Element>(
    cssSelector: CssSelector,
    domSource: DomSource<A, B>
  ): DomSource<C, B>
  <A = Element, B = Event, C = Element>(cssSelector: CssSelector): (
    domSource: DomSource<A, B>
  ) => DomSource<C, B>
  (cssSelector: CssSelector): <A = Element, B = Event, C = Element>(
    domSource: DomSource<A, B>
  ) => DomSource<C, B>
}


useCapture<A = Element, B = Event>(dom: DomSource<A, B>): DomSource<A, B>

Creates a new DomSource that will default to using capture when using events().

See an example
import { useCapture, events } from '@motorcycle/dom'

export function Component(sources) {
  const { dom } = sources

  const click$ = events('click', useCapture(dom))

  ...
}
See the code

export function useCapture<A = Element, B = Event>(dom: DomSource<A, B>): DomSource<A, B> {
  const useCaptureDomSource: DomSource<A, B> = {
    query(cssSelector: CssSelector): DomSource<A, B> {
      return dom.query(cssSelector)
    },

    elements(): Stream<ReadonlyArray<A>> {
      return dom.elements()
    },

    events<Ev extends B = B>(
      eventType: StandardEvents,
      options: EventListenerOptions = { capture: true }
    ): Stream<Ev> {
      return dom.events(eventType, options)
    },

    cssSelectors(): ReadonlyArray<CssSelector> {
      return dom.cssSelectors()
    },
  }

  return useCaptureDomSource
}