npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@motorcycle/dom

v17.0.0

Published

Declarative, functional, reactive abstractions for the DOM

Downloads

94

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.

const queriedDomSource = domSource.query(`.myCssSelector`)
const cssSelectors = queriedDomSource.cssSelectors()

console.log(cssSelectors[0]) // .myCssSelector

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.

const queriedDomSource = domSource.query(`.myCssSelector`)
const elements$ = queriedDomSource.elements()

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.

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

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.

const queriedDomSource = domSource.query(`.myCssSelector`)

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.

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$)
  }
}

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.

import { createDomSource } from '@motorcycle/dom'

const dom = createDomSource(element$)

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.

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$)
  }
}

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.

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

type Sources = { dom: DomSource } 

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

  const elements$ = elements(dom)

  ...
}

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.

import { events } from '@motorcycle/dom'

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

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.

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)

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.

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)

  ...
}

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().

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

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

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

  ...
}

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
}