tunnckoCore/mich-h

View on GitHub
src/index.js

Summary

Maintainability
B
4 hrs
Test Coverage
/*!
 * mich-h <https://github.com/tunnckoCore/mich-h>
 *
 * Copyright (c) Charlike Mike Reagent <@tunnckoCore> (https://i.am.charlike.online)
 * Released under the MIT license.
 */

import parseSelector from 'mich-parse-selector'

/**
 * > Virtual DOM builder that is compatible to [hyperscript][],
 * so it takes any number of arguments that can be string,
 * object, or array. But the first one `selector` always should
 * be a simple css-like selector supported by [mich-parse-selector][].
 * For example `.foo.bar` creates a node with tag name `div`
 * and classes `foo bar`. Or selector like `p.foo#hero.bar` creates
 * a node with id `hero`, classes `foo` and `bar`, and tag name `p`.
 *
 * **Example**
 *
 * ```js
 * const h = require('mich-h')
 *
 * const node = h('a.foo#brand.bar.btn-large.xyz', {
 *   className: 'btn'
 *   href: 'https://i.am.charlike.online'
 * }, 'Charlike Web')
 *
 * console.log(node.type) // => 'element'
 * console.log(node.tagName) // => 'p'
 * console.log(node.properties.id) // => 'brand'
 *
 * console.log(node.properties.href)
 * // => 'https://i.am.charlike.online'
 *
 * console.log(node.properties.className)
 * // => [ 'foo', 'bar', 'btn-large', 'xyz', 'btn' ]
 *
 * console.log(node.children.length) // => 1
 * console.log(node.children[0])
 * // => { type: 'text', value: 'Charlike Web' }
 * ```
 *
 * @name   michH
 * @param  {String} `selector` simple selector; supports IDs, classes and tag name only
 * @param  {Object} `props` an attributes for the tag; can be in any position (i.e 4th)
 * @param  {String|Array} `children` a child nodes; can be in any position (i.e. 2nd or 5th)
 * @return {Object} a HAST compliant node
 * @api public
 */

export default function michH (selector) {
  var isComponent = typeof selector === 'function'
  var node = isComponent ? parseSelector() : parseSelector(selector)
  var args = [].slice.call(arguments, 1)

  function item (arg) {
    if (arg) {
      if (arg.map) {
        while (arg.length) {
          addChild(node.children, arg.shift())
        }
      } else if (typeof arg === 'object' && !isNode(arg)) {
        for (var prop in arg) {
          addProperty(node.properties, prop, arg[prop])
        }
      } else {
        addChild(node.children, arg)
      }
    }
  }

  while (args.length) {
    item(args.shift())
  }

  if (isComponent) {
    node.properties.children = node.children
    return selector(node.properties, node.properties.children)
  }

  return node
}

function isNode (val) {
  return val.tagName &&
    val.properties &&
    typeof val.properties === 'object' &&
    Array.isArray(val.children) &&
    val.type === 'element'
}

function addChild (nodes, children) {
  var type = typeof children
  if (type === 'string' || type === 'number') {
    children = {
      type: 'text',
      value: children + ''
    }
  }
  nodes.push(children)
}

function addProperty (props, key, value) {
  if (key === 'style' && value && typeof value === 'object') {
    props.style = props.style || {}
    for (var k in value) {
      props.style[k] = value[k]
    }
  }

  if (key === 'className') {
    value = typeof value === 'string' ? value.split(' ') : value
    props[key] = (props[key] || []).concat(value)
  } else if (key.slice(0, 5) === 'data-') {
    props.dataset = props.dataset || {}
    props.dataset[key.slice(5)] = value
  } else {
    props[key] = value
  }
}