function leaf (item, index = 0, rootListeners = {}) {
  if (item && typeof item === 'object' && !Array.isArray(item)) {
    let { children = null, component = 'FormulateInput', depth = 1, key = null, ...attrs } = item
    // these next two lines are required since `class` is a keyword and should
    // not be used in rest/spread operators.
    const cls = attrs.class || {}
    delete attrs.class
    // Event bindings
    const on = {}

    // Extract events from this instance
    const events = Object.keys(attrs)
      .reduce((events, key) => /^@/.test(key) ? Object.assign(events, { [key.substr(1)]: attrs[key] }) : events, {})

    // delete all events from the item
    Object.keys(events).forEach(event => {
      delete attrs[`@${event}`]
      on[event] = createListener(event, events[event], rootListeners)
    })

    const type = component === 'FormulateInput' ? (attrs.type || 'text') : component
    const name = attrs.name || type || 'el'
    if (!key) {
      // We need to generate a unique key if at all possible
      if (attrs.id) {
        // We've been given an id, so we should use it.
        key = attrs.id
      } else if (component !== 'FormulateInput' && typeof children === 'string') {
        // This is a simple text node container.
        key = `${type}-${cyrb43(children)}`
      } else {
        // This is a wrapper element
        key = `${type}-${name}-${depth}${attrs.name ? '' : '-' + index}`
      }
    }
    const els = Array.isArray(children)
      ? children.map(child => Object.assign(child, { depth: depth + 1 }))
      : children
    return Object.assign({ key, depth, attrs, component, class: cls, on }, els ? { children: els } : {})
  }
  return null
}
Given an object and an index, complete an object for schema-generation.
@param {object} item
@param {int} index