src/State.js
function State(options) {
  const state = { options }
  const states = options.children

  state.path = pathFromURI(options.uri)
  state.params = paramsFromURI(options.uri)
  state.queryParams = queryParamsFromURI(options.uri)
  state.states = states
  state.data = options.data

  state.enter = options.enter || util.noop
  state.update = options.update
  state.exit = options.exit || util.noop

  /*
  * Initialize and freeze this state.
  */
  function init(router, name, parent) {
    state.router = router
    state.name = name
    state.isDefault = name == '_default_'
    state.parent = parent
    state.parents = getParents()
    state.root = state.parent ? state.parents[state.parents.length - 1] : state
    state.children = util.objectToArray(states)
    state.fullName = getFullName()
    state.asPublic = makePublicAPI()

    eachChildState((name, childState) => {
      childState.init(router, name, state)
    })
  }

  /*
  * The full path, composed of all the individual paths of this state and its parents.
  */
  function fullPath() {
    let result = state.path
    let stateParent = state.parent

    while (stateParent) {
      if (stateParent.path) result = stateParent.path + '/' + result
      stateParent = stateParent.parent
    }

    return result
  }

  /*
  * The list of all parents, starting from the closest ones.
  */
  function getParents() {
    const parents = []
    let parent = state.parent

    while (parent) {
      parents.push(parent)
      parent = parent.parent
    }

    return parents
  }

  /*
  * The fully qualified name of this state.
  * e.g granparentName.parentName.name
  */
  function getFullName() {
    const result = state.parents.reduceRight((acc, parent) => {
      return acc + parent.name + '.'
    }, '') + state.name

    return state.isDefault
      ? result.replace('._default_', '')
      : result
  }

  function allQueryParams() {
    return state.parents.reduce((acc, parent) => {
      return util.mergeObjects(acc, parent.queryParams)
    }, util.copyObject(state.queryParams))
  }

  function makePublicAPI() {
    return {
      name: state.name,
      fullName: state.fullName,
      data: options.data || {},
      parent: state.parent && state.parent.asPublic
    }
  }

  function eachChildState(callback) {
    for (let name in states) callback(name, states[name])
  }

  /*
  * Returns whether this state matches the passed path Array.
  * In case of a match, the actual param values are returned.
  */
  function matches(paths) {
    const params = {}
    const nonRestStatePaths = state.paths.filter(p => p[p.length - 1] !== '*')

    /* This state has more paths than the passed paths, it cannot be a match */
    if (nonRestStatePaths.length > paths.length) return false

    /* Checks if the paths match one by one */
    for (let i = 0; i < paths.length; i++) {
      const path = paths[i]
      const thatPath = state.paths[i]

      /* This state has less paths than the passed paths, it cannot be a match */
      if (!thatPath) return false

      const isRest = thatPath[thatPath.length - 1] === '*'
      if (isRest) {
        const name = paramName(thatPath)
        params[name] = paths.slice(i).join('/')
        return params
      }

      const isDynamic = thatPath[0] === ':'
      if (isDynamic) {
        const name = paramName(thatPath)
        params[name] = path
      }
      else if (thatPath != path) return false
    }

    return params
  }

  /*
  * Returns a URI built from this state and the passed params.
  */
  function interpolate(params) {
    const path = state.fullPath().replace(PARAMS, p => params[paramName(p)] || '')

    const queryParams = allQueryParams()
    const passedQueryParams = Object.keys(params).filter(p => queryParams[p])

    const query = passedQueryParams.map(p => p + '=' + params[p]).join('&')

    return path + (query.length ? ('?' + query) : '')
  }


  function toString() {
    return state.fullName
  }


  state.init = init
  state.fullPath = fullPath
  state.allQueryParams = allQueryParams
  state.matches = matches
  state.interpolate = interpolate
  state.toString = toString

  return state
}
function State(options) {
  const state = { options }
  const states = options.children

  state.path = pathFromURI(options.uri)
  state.params = paramsFromURI(options.uri)
  state.queryParams = queryParamsFromURI(options.uri)
  state.states = states
  state.data = options.data

  state.enter = options.enter || util.noop
  state.update = options.update
  state.exit = options.exit || util.noop

  /*
  * Initialize and freeze this state.
  */
  function init(router, name, parent) {
    state.router = router
    state.name = name
    state.isDefault = name == '_default_'
    state.parent = parent
    state.parents = getParents()
    state.root = state.parent ? state.parents[state.parents.length - 1] : state
    state.children = util.objectToArray(states)
    state.fullName = getFullName()
    state.asPublic = makePublicAPI()

    eachChildState((name, childState) => {
      childState.init(router, name, state)
    })
  }

  /*
  * The full path, composed of all the individual paths of this state and its parents.
  */
  function fullPath() {
    let result = state.path
    let stateParent = state.parent

    while (stateParent) {
      if (stateParent.path) result = stateParent.path + '/' + result
      stateParent = stateParent.parent
    }

    return result
  }

  /*
  * The list of all parents, starting from the closest ones.
  */
  function getParents() {
    const parents = []
    let parent = state.parent

    while (parent) {
      parents.push(parent)
      parent = parent.parent
    }

    return parents
  }

  /*
  * The fully qualified name of this state.
  * e.g granparentName.parentName.name
  */
  function getFullName() {
    const result = state.parents.reduceRight((acc, parent) => {
      return acc + parent.name + '.'
    }, '') + state.name

    return state.isDefault
      ? result.replace('._default_', '')
      : result
  }

  function allQueryParams() {
    return state.parents.reduce((acc, parent) => {
      return util.mergeObjects(acc, parent.queryParams)
    }, util.copyObject(state.queryParams))
  }

  function makePublicAPI() {
    return {
      name: state.name,
      fullName: state.fullName,
      data: options.data || {},
      parent: state.parent && state.parent.asPublic
    }
  }

  function eachChildState(callback) {
    for (let name in states) callback(name, states[name])
  }

  /*
  * Returns whether this state matches the passed path Array.
  * In case of a match, the actual param values are returned.
  */
  function matches(paths) {
    const params = {}
    const nonRestStatePaths = state.paths.filter(p => p[p.length - 1] !== '*')

    /* This state has more paths than the passed paths, it cannot be a match */
    if (nonRestStatePaths.length > paths.length) return false

    /* Checks if the paths match one by one */
    for (let i = 0; i < paths.length; i++) {
      const path = paths[i]
      const thatPath = state.paths[i]

      /* This state has less paths than the passed paths, it cannot be a match */
      if (!thatPath) return false

      const isRest = thatPath[thatPath.length - 1] === '*'
      if (isRest) {
        const name = paramName(thatPath)
        params[name] = paths.slice(i).join('/')
        return params
      }

      const isDynamic = thatPath[0] === ':'
      if (isDynamic) {
        const name = paramName(thatPath)
        params[name] = path
      }
      else if (thatPath != path) return false
    }

    return params
  }

  /*
  * Returns a URI built from this state and the passed params.
  */
  function interpolate(params) {
    const path = state.fullPath().replace(PARAMS, p => params[paramName(p)] || '')

    const queryParams = allQueryParams()
    const passedQueryParams = Object.keys(params).filter(p => queryParams[p])

    const query = passedQueryParams.map(p => p + '=' + params[p]).join('&')

    return path + (query.length ? ('?' + query) : '')
  }


  function toString() {
    return state.fullName
  }


  state.init = init
  state.fullPath = fullPath
  state.allQueryParams = allQueryParams
  state.matches = matches
  state.interpolate = interpolate
  state.toString = toString

  return state
}
function State ( options ) { const state = { options } const states = options . children state . path = pathFromURI ( options . uri ) state . params = paramsFromURI ( options . uri ) state . queryParams = queryParamsFromURI ( options . uri ) state . states = states state . data = options . data state . enter = options . enter || util . noop state . update = options . update state . exit = options . exit || util . noop /*
  * Initialize and freeze this state.
  */ function init ( router , name , parent ) { state . router = router state . name = name state . isDefault = name == '_default_' state . parent = parent state . parents = getParents ( ) state . root = state . parent ? state . parents [ state . parents . length - 1 ] : state state . children = util . objectToArray ( states ) state . fullName = getFullName ( ) state . asPublic = makePublicAPI ( ) eachChildState ( ( name , childState ) => { childState . init ( router , name , state ) } ) } /*
  * The full path, composed of all the individual paths of this state and its parents.
  */ function fullPath ( ) { let result = state . path let stateParent = state . parent while ( stateParent ) { if ( stateParent . path ) result = stateParent . path + '/' + result stateParent = stateParent . parent } return result } /*
  * The list of all parents, starting from the closest ones.
  */ function getParents ( ) { const parents = [ ] let parent = state . parent while ( parent ) { parents . push ( parent ) parent = parent . parent } return parents } /*
  * The fully qualified name of this state.
  * e.g granparentName.parentName.name
  */ function getFullName ( ) { const result = state . parents . reduceRight ( ( acc , parent ) => { return acc + parent . name + '.' } , '' ) + state . name return state . isDefault ? result . replace ( '._default_' , '' ) : result } function allQueryParams ( ) { return state . parents . reduce ( ( acc , parent ) => { return util . mergeObjects ( acc , parent . queryParams ) } , util . copyObject ( state . queryParams ) ) } function makePublicAPI ( ) { return { name : state . name , fullName : state . fullName , data : options . data || { } , parent : state . parent && state . parent . asPublic } } function eachChildState ( callback ) { for ( let name in states ) callback ( name , states [ name ] ) } /*
  * Returns whether this state matches the passed path Array.
  * In case of a match, the actual param values are returned.
  */ function matches ( paths ) { const params = { } const nonRestStatePaths = state . paths . filter ( p => p [ p . length - 1 ] !== '*' ) /* This state has more paths than the passed paths, it cannot be a match */ if ( nonRestStatePaths . length > paths . length ) return false /* Checks if the paths match one by one */ for ( let i = 0 ; i < paths . length ; i ++ ) { const path = paths [ i ] const thatPath = state . paths [ i ] /* This state has less paths than the passed paths, it cannot be a match */ if ( ! thatPath ) return false const isRest = thatPath [ thatPath . length - 1 ] === '*' if ( isRest ) { const name = paramName ( thatPath ) params [ name ] = paths . slice ( i ) . join ( '/' ) return params } const isDynamic = thatPath [ 0 ] === ':' if ( isDynamic ) { const name = paramName ( thatPath ) params [ name ] = path } else if ( thatPath != path ) return false } return params } /*
  * Returns a URI built from this state and the passed params.
  */ function interpolate ( params ) { const path = state . fullPath ( ) . replace ( PARAMS , p => params [ paramName ( p ) ] || '' ) const queryParams = allQueryParams ( ) const passedQueryParams = Object . keys ( params ) . filter ( p => queryParams [ p ] ) const query = passedQueryParams . map ( p => p + '=' + params [ p ] ) . join ( '&' ) return path + ( query . length ? ( '?' + query ) : '' ) } function toString ( ) { return state . fullName } state . init = init state . fullPath = fullPath state . allQueryParams = allQueryParams state . matches = matches state . interpolate = interpolate state . toString = toString return state }
/*
Creates a new State instance from a {uri, enter, exit, update, children} object.
This is the internal representation of a state used by the router.
/ * Creates a new State instance from a { uri enter exit update children } object . This is the internal representation of a state used by the router .