⌈⌋ ⎇ branch:  Bitrhythm


Artifact Content

Artifact 4b6245ca922fdaf60af6d465f61c646da143c7626c6bd78130284681a81be836:


import styleManager from './styleManager'
import isString from './../common/util/checks/is-string'
import isUndefined from './../common/util/checks/is-undefined'
import isObject from './../common/util/checks/is-object'
import isFunction from './../common/util/checks/is-function'
import setAttribute from './../common/util/dom/set-attribute'
import getAttribute from './../common/util/dom/get-attribute'
import $$ from './../common/util/dom/$$'
import each from './../common/util/misc/each'
import extend from './../common/util/misc/extend'
import mountTo from './../common/util/tags/mount'
import query from './../common/util/tags/query'

import {
  __TAG_IMPL,
  __TAGS_CACHE,
  GLOBAL_MIXIN,
  IS_DIRECTIVE
} from './../common/global-variables'

/**
 * Another way to create a riot tag a bit more es6 friendly
 * @param { HTMLElement } el - tag DOM selector or DOM node/s
 * @param { Object } opts - tag logic
 * @returns { Tag } new riot tag instance
 */
export function Tag(el, opts) {
  // get the tag properties from the class constructor
  const {name, tmpl, css, attrs, onCreate} = this
  // register a new tag and cache the class prototype
  if (!__TAG_IMPL[name]) {
    tag(name, tmpl, css, attrs, onCreate)
    // cache the class constructor
    __TAG_IMPL[name].class = this.constructor
  }

  // mount the tag using the class instance
  mountTo(el, name, opts, this)
  // inject the component css
  if (css) styleManager.inject()

  return this
}

/**
 * Create a new riot tag implementation
 * @param   { String }   name - name/id of the new riot tag
 * @param   { String }   tmpl - tag template
 * @param   { String }   css - custom tag css
 * @param   { String }   attrs - root tag attributes
 * @param   { Function } fn - user function
 * @returns { String } name/id of the tag just created
 */
export function tag(name, tmpl, css, attrs, fn) {
  if (isFunction(attrs)) {
    fn = attrs

    if (/^[\w-]+\s?=/.test(css)) {
      attrs = css
      css = ''
    } else
      attrs = ''
  }

  if (css) {
    if (isFunction(css))
      fn = css
    else
      styleManager.add(css, name)
  }

  name = name.toLowerCase()
  __TAG_IMPL[name] = { name, tmpl, attrs, fn }

  return name
}

/**
 * Create a new riot tag implementation (for use by the compiler)
 * @param   { String }   name - name/id of the new riot tag
 * @param   { String }   tmpl - tag template
 * @param   { String }   css - custom tag css
 * @param   { String }   attrs - root tag attributes
 * @param   { Function } fn - user function
 * @returns { String } name/id of the tag just created
 */
export function tag2(name, tmpl, css, attrs, fn) {
  if (css) styleManager.add(css, name)

  __TAG_IMPL[name] = { name, tmpl, attrs, fn }

  return name
}

/**
 * Mount a tag using a specific tag implementation
 * @param   { * } selector - tag DOM selector or DOM node/s
 * @param   { String } tagName - tag implementation name
 * @param   { Object } opts - tag logic
 * @returns { Array } new tags instances
 */
export function mount(selector, tagName, opts) {
  const tags = []
  let elem, allTags

  function pushTagsTo(root) {
    if (root.tagName) {
      let riotTag = getAttribute(root, IS_DIRECTIVE), tag

      // have tagName? force riot-tag to be the same
      if (tagName && riotTag !== tagName) {
        riotTag = tagName
        setAttribute(root, IS_DIRECTIVE, tagName)
      }

      tag = mountTo(
        root,
        riotTag || root.tagName.toLowerCase(),
        isFunction(opts) ? opts() : opts
      )

      if (tag)
        tags.push(tag)
    } else if (root.length)
      each(root, pushTagsTo) // assume nodeList
  }

  // inject styles into DOM
  styleManager.inject()

  if (isObject(tagName) || isFunction(tagName)) {
    opts = tagName
    tagName = 0
  }

  // crawl the DOM to find the tag
  if (isString(selector)) {
    selector = selector === '*' ?
      // select all registered tags
      // & tags found with the riot-tag attribute set
      allTags = query() :
      // or just the ones named like the selector
      selector + query(selector.split(/, */))

    // make sure to pass always a selector
    // to the querySelectorAll function
    elem = selector ? $$(selector) : []
  }
  else
    // probably you have passed already a tag or a NodeList
    elem = selector

  // select all the registered and mount them inside their root elements
  if (tagName === '*') {
    // get all custom tags
    tagName = allTags || query()
    // if the root els it's just a single tag
    if (elem.tagName)
      elem = $$(tagName, elem)
    else {
      // select all the children for all the different root elements
      var nodeList = []

      each(elem, _el => nodeList.push($$(tagName, _el)))

      elem = nodeList
    }
    // get rid of the tagName
    tagName = 0
  }

  pushTagsTo(elem)

  return tags
}

// Create a mixin that could be globally shared across all the tags
const mixins = {}
const globals = mixins[GLOBAL_MIXIN] = {}
let mixins_id = 0

/**
 * Create/Return a mixin by its name
 * @param   { String }  name - mixin name (global mixin if object)
 * @param   { Object }  mix - mixin logic
 * @param   { Boolean } g - is global?
 * @returns { Object }  the mixin logic
 */
export function mixin(name, mix, g) {
  // Unnamed global
  if (isObject(name)) {
    mixin(`__${mixins_id++}__`, name, true)
    return
  }

  const store = g ? globals : mixins

  // Getter
  if (!mix) {
    if (isUndefined(store[name]))
      throw new Error(`Unregistered mixin: ${ name }`)

    return store[name]
  }

  // Setter
  store[name] = isFunction(mix) ?
    extend(mix.prototype, store[name] || {}) && mix :
    extend(store[name] || {}, mix)
}

/**
 * Update all the tags instances created
 * @returns { Array } all the tags instances
 */
export function update() {
  return each(__TAGS_CACHE, tag => tag.update())
}

export function unregister(name) {
  styleManager.remove(name)
  return delete __TAG_IMPL[name]
}

export const version = 'WIP'