core.js

import {Data, Event, Selector} from './Classes'
import pkg from '../package.json'
import {
	camelCase,
	createScript,
	each,
	empty,
	extend,
	formatBytes,
	getStyleComputed,
	hasProp,
	isArrayish,
	isElement,
	isFunction,
	isFascinoElement,
	isNumber,
	isObject,
	isSelector,
	isString,
	isVisible,
	merge,
	normName,
	normalizeData,
	normalizeElements,
	not,
	parseHTML,
	script
}  from './Utils/Utils.js'

if (!Element.prototype.matches) {
	Element.prototype.matches =
		Element.prototype.matchesSelector ||
		Element.prototype.mozMatchesSelector ||
		Element.prototype.msMatchesSelector ||
		Element.prototype.oMatchesSelector ||
		Element.prototype.webkitMatchesSelector ||
		function(s) {
			let matches = (this.document || this.ownerDocument).querySelectorAll(s),
				i = matches.length
			while (--i >= 0 && matches.item(i) !== this) { }
			return i > -1
		}
}

if (!Element.prototype.closest) {
	Element.prototype.closest = function(s) {
		let el = this

		do {
			if (el.matches(s)) return el
			el = el.parentElement || el.parentNode
		} while (el !== null && el.nodeType === 1)
		return null
	}
}

/**
 * Lista de Eventos Nativos de Javascript <br>
 * Aquí se Almacenan los nombre de las funciones de eventos nativos 
 * 'blur', 'focus', 'resize', 'scroll', 'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
 * 'mouseover', 'mouseout', 'mouseenter', 'mouseleave', 'change', 'select', 'submit', 'keydown',
 * 'keypress', 'keyup', 'contextmenu', 'touchstart', 'touchend', 'touchmove', 'touchcancel'
 * @example <caption>Uso</caption>
 * _$('selector').clicK(function(e){})
 * @memberOf Fascino
 * @private
 * @type {Array}
 */
const ListEvents = [
	'blur', 'focus', 'resize', 'scroll', 'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
	'mouseover', 'mouseout', 'mouseenter', 'mouseleave', 'change', 'select', 'submit', 'keydown',
	'keypress', 'keyup', 'contextmenu', 'touchstart', 'touchend', 'touchmove', 'touchcancel'
]
/**
 * @memberOf Fascino
 * @private
 * @const {Number}
 * @default 3
 */
const NODETEXT = 3
/**
 * Función que reinvoca la Clase Fascino
 * @memberOf Fascino
 * @private
 * @param  {(String|Element|Array|Function|Object)} selector
 * @param  {HTMLElement} context
 * @return {Fascino}
 */
function w(sel, ctx){
	return new Fascino(sel, ctx);
}

/**
 * Fascino, Encantador Framework JS para su fácil uso
 * @global
 * @namespace Fascino
 * @class
 * @extends {module:Core.Selector}
 */
export class Fascino extends Selector{
	_ds = new Data()
	_ev = new Event()
	/**
	 * @param  {(String|Element|Array|Function|Object)} selector Seletor, Elemento ó funcion para iniciar FascinoJs
	 * @param  {HTMLElement} context Contexto del selector, por defecto es <code>document</code>
	 * @return {Fascino}
	 */
	constructor(selector, context = document) {
		super(selector, context)
		this.name = 'FascinoJS'
		this.length = this.Elem.length
		this.version = pkg.version
		if (this.length == 1) {
			this.events = this.getEvent()
		}
	}
	// STATICOS

	/**
	 * Añade funciones a Fascino
	 * @memberOf Fascino
	 * @public
	 * @param {String} name Nombre de la función
	 * @param {Function} fn Función a asignar
	 * @return {Fascino}
	 */
	static addFn(name, fn) {
		if (!hasProp(this, name)) {
			this.prototype[name] = fn
		}

		return this		
	}

	// PRIVADOS
	/**
	 * Private _is
	 * @memberOf Fascino
	 * @private
	 * @param  {String}  prop propiedad tipo
	 * @return {Boolean}
	 */
	_is(prop) {
		let res = false
		this.each((el) => {
			res = el[prop]
		})
		return res
	}
	/**
	 * Busca y obtiene una propiedad de un elemento dado
	 * @memberOf Fascino
	 * @private
	 * @param  {String} n Nombre de la propiedad
	 * @param  {String} v Valor de la propiedad
	 * @return {Fascino}
	 */
	_prop(n, v) {
		if (this.length === 0) {
			return this
		}

		if (arguments.length === 1) {
			return n in this.Elem[0] ? this.Elem[0][n] : undefined
		}

		if (empty(v)) {
			v = ''
		}

		return this.each((el) => {
			el[n] = v
			if (n === 'innerHTML') {
				script(el)
			}
		})
	}

	/**
	 * Método privado que ayuda a establecer opciones a un elemento dado.
	 * @memberOf Fascino
	 * @private
	 * @param {(Element|NodeList)} newNode
	 * @param {Object} options
	 */
	_setOptions(newNode, options) {
		if (!empty(options)) {
			for (let key in options) {
				if (hasProp(options, key)) {
					let value = options[key]
					if (hasProp(this, key)) {
						w(newNode)[key].apply(this, value)
					}
				}
			}
		}
	}
	/**
	 * Tamaño Externo del elemento
	 * @memberOf Fascino
	 * @private
	 * @param  {String} prop
	 * @param  {String} val
	 * @return {(String|Number)}
	 */
	_sizeOut(prop, val) {
		let el, size, style, result

		if (this.length === 0) {
			return
		}

		if (!empty(val) && typeof val !== 'boolean') {
			return this.each((el) => {
				if (el === window || el === document) {
					return
				}
				let h, style = getStyleComputed(el),
					bs = prop === 'width' ? parseInt(style['border-left-width']) + parseInt(style['border-right-width']) : parseInt(style['border-top-width']) + parseInt(style['border-bottom-width']),
					pa = prop === 'width' ? parseInt(style['padding-left']) + parseInt(style['padding-right']) : parseInt(style['padding-top']) + parseInt(style['padding-bottom'])

				h = w(el)[prop](val)[prop]() - bs - pa
				el.style[prop] = h + 'px'
			})
		}

		el = this.Elem[0]
		size = el[prop === 'width' ? 'offsetWidth' : 'offsetHeight']
		style = getStyleComputed(el)
		result = size + parseInt(style[prop === 'width' ? 'margin-left' : 'margin-top']) + parseInt(style[prop === 'width' ? 'margin-right' : 'margin-bottom'])
		return val === true ? result : size
	}
	/**
	 * Tamano real del elemento
	 * @memberOf Fascino
	 * @private
	 * @param  {String} pro
	 * @param  {String} val
	 * @return {(fascino|Number|NaN|String)}
	 */
	_size(prop, val) {
		if (this.length === 0) {
			return NaN
		}

		if (empty(val)) {
			let el = this.Elem[0]
			if (prop === 'height') {
				return el === window ? window.innerHeight : el === document ? el.body.clientHeight : parseInt(getComputedStyle(el).height)
			} else if (prop === 'width') {
				return el === window ? window.innerWidth : el === document ? el.body.clientWidth : parseInt(getComputedStyle(el).width)
			}
		}

		return this.each((el) => {
			if (el === window || el === document) {
				return
			}
			el.style[prop] = isNaN(val) ? val : val + 'px'
		})
	}
	// PUBLICOS
	/**
	 * Recorre los elementos
	 * @memberOf Fascino
	 * @public
	 * @param  {...(Function|Array)} arg Argumentos
	 * @return {Fascino}
	 */
	each(...arg) {
		this.Elem.forEach(...arg)
		return this
	}
	/**
	 * Verifica si el elemento es seleccionable por el Selector
	 * @memberOf Fascino
	 * @public
	 * @param  {String} selectorString Selector CSS
	 * @return {(Element|Array)}
	 */
	matches(selectorString) {
		const elem = []
		this.each((el) => {
			if (el.matches(selectorString)) {
				elem.push(el)
			}
		})
		return elem
	}
	/**
	 * Crea una nueva matriz de elementos a través de la función dada
	 * @memberOf Fascino
	 * @public
	 * @param  {Function} callback Función
	 * @return {Array}
	 */
	map(callback) {
		return this.Elem.map(callback)
	}
	/**
	 * Combina Elementos
	 * @memberOf Fascino
	 * @public
	 * @param  {Array} els Matriz de elementos nueva
	 * @return {Fascino}
	 */
	merge(els) {
		merge(this.Elem, els)
		return this
	}
	/**
	 * Obtiene la posición del elemento dentro de su padre
	 * @memberOf Fascino
	 * @public
	 * @param  {(Element|Null)} parent   El padre de los Elementos
	 * @param  {String} nodeName tipo de nombre de nodo entre los cuales buscar
	 * @return {Number}
	 */
	index(parent, nodeName) {
		let el = this.Elem[0], index = -1, child
		parent = empty(parent) ? el.parentNode() : normalizeElements(parent)

		if (empty(nodeName)) {
			child = w(parent).children()
		} else {
			child = w(parent).find(nodeName)
		}

		child.each((element, i) => {
			if (element === el) {
				index = i
			}
		})

		return index
	}
	/**
	 * Obtiene el Elemento solicitado por su posición dentro de la matriz de Elementos, El Elemeto obtenido es de tipo Element
	 * @memberOf Fascino
	 * @public
	 * @param  {Number} i Posición
	 * @return {Element}
	 */
	get(i) {
		if (not(i)) {
			return this.Elem
		}
		return i < 0 ? this.Elem[i + this.length] : this.Elem[i]
	}
	/**
	 * Busca, valida y obtiene el elemento dado por su posición dentro de la matriz de elementos
	 * @memberOf Fascino
	 * @public
	 * @param  {Number} i posición del elemento
	 * @return {Fascino}
	 */
	eq(i) {
		return !isNaN(i) && this.length > 0 ? extend(w(this.get(i)), { _prevObj: this }) : this
	}
	/**
	 * Obtiene el ultimo elemento de la matriz
	 * @memberOf Fascino
	 * @public
	 * @return {Element}
	 */
	last() {
		return this.eq(this.length - 1)
	}
	/**
	 * Obtiene el primer elemento de la matriz
	 * @memberOf Fascino
	 * @public
	 * @return {Element}
	 */
	first() {
		return this.eq(0)
	}
	/**
	 * Crea una nueva selección de elemento que cumplan con la condición dada en la función
	 * @memberOf Fascino
	 * @public
	 * @param  {Function} fn Función para filtrar
	 * @return {Fascino}
	 */
	filter(fn) {
		if (isString(fn)) {
			let sel = fn
			fn = function(el) {
				return el.matches(sel)
			}
		}

		return extend(
			merge(w().Elem, [].filter.call(this.Elem, fn)),
			{ _prevObj: this })
	}
	/**
	 * Obtiene los elementos impares de la matriz
	 * @memberOf Fascino
	 * @public
	 * @return {(Element|Fascino)}
	 */
	odd() {
		let result = this.filter(function(el, i) {
			return i % 2 === 0
		})
		return extend(result, { _prevObj: this })
	}
	/**
	 * Obtiene los numero pares de la matriz
	 * @memberOf Fascino
	 * @public
	 * @return {(Element|Fascino)}
	 */
	even() {
		let result = this.filter(function(el, i) {
			return i % 2 !== 0
		})
		return extend(result, { _prevObj: this })
	}
	/**
	 * Busca un elemento hijo por su selector CSS
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Element)} sel Selector CSS valido
	 * @return {Fascino}
	 */
	find(sel) {
		let rEl = Array.from('')

		this.Elem.forEach((el)=>{
			rEl.push(
				[].concat(...Element.prototype.querySelectorAll.call(el, sel))
			)
		})

		return rEl.length === 0 ? [] : rEl.length === 1 ? w(rEl[0]) : merge(w().Elem, rEl)
	}
	/**
	 * Verifica si el elemento es hijo del Elemento seleccionado
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Element)} s Selector CSS
	 * @return {Boolean}
	 */
	contains(s) {
		return this.find(s).length > 0
	}
	/**
	 * Obtiene los hijos de un elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Element)} sel Selector o elemento hijo a buscar
	 * @return {Array} Lista de hijos
	 */
	children(sel) {
		let element = this.Elem[0]
		if (empty(sel)) {
			return [].concat(...element.children)
		}


		return [].concat(...element.children)
			.filter(child => child.matches(sel))
	}
	/**
	 * Verifica de que tipo es el selector
	 * @memberOf Fascino
	 * @public
	 * @param  {*}  s
	 * @return {Boolean}
	 */
	is(s) {
		let result = false
		if (this.length === 0 ) {
			return false
		}

		if (s && hasProp(s, 'Elem')) {
			this.each((el) => {
				s.each((o) => {
					if (el === o) {
						result = true
						return
					}
				})
			})
		}

		if (isString(s)) {
			if (s === ':selected') {
				result = this._is('selected')
			} else if (s === ':checked') {
				result = this._is('checked')
			} else if (s === ':visible') {
				this.each((el) => {
					if (isVisible(el)) {
						result = true
					}
				})
			} else if (s === ':hidden') {
				this.each((el) => {
					if (el.getAttribute('type') === 'hidden' || isHiden(el)) {
						result = true
					}
				})
			} else if (s === ':disabled') {
				this.each((el) => {
					if (el.getAttribute('disabled') || _$(el).hasClass('disabled')) {
						result = true
					}
				})
			} else if (s === ':readonly') {
				this.each((el) => {
					if (el.getAttribute('readonly') || _$(el).hasClass('readonly')) {
						result = true
					}
				})
			} else {
				this.each((el) => {
					if (el.matches(s) || Element.prototype.matches.call(el, s)) {
						result = true
					}
				})
			}
		} else if (isArrayish(s)) {
			this.each((el) => {
				s.forEach((sel) => {
					if (el === sel) {
						result = true
					}
				})
			})
		} else if (s.nodeType === 1) {
			this.each((el) => {
				if (el === s) {
					result = true
				}
			})
		}

		return result
	}
	/**
	 * Método público de Fascino._prop
	 * @memberOf Fascino
	 * @public
	 * @param  {String} n
	 * @param  {String} v
	 * @return {Fascino}
	 */
	prop(n, v) {
		return arguments.length === 1 ?
			this._prop(n) :
			this._prop(n, empty(v) ? '' : v)
	}
	/**
	 * Agrega un elemento al padre seleccionado
	 * @memberOf Fascino
	 * @public
	 * @param  {(Element|Fascino|String)} node
	 * @param  {Object} options
	 * @return {Fascino}
	 */
	append(node, options) {
		if (this.length === 0) {
			return this
		}
		let newNode = normalizeElements(node)

		this._setOptions(newNode, options)

		return this.each((el) => {
			w(newNode).each((nN, i) => {
				if (nN === el) {
					return
				}
				let child = i === 0 ? nN : nN.cloneNode(true)
				script(child)
				if (child.tagName && child.tagName !== 'SCRIPT') {
					el.append(child)
				}
			})
		})
	}
	/**
	 * Agrega el elemento seleccionado al nuevo padre
	 * @memberOf Fascino
	 * @public
	 * @param  {(Element|Fascino|String)} node
	 * @param  {Object} options
	 * @return {Fascino}
	 */
	appendTo(node, options) {
		if (this.length === 0) {
			return this
		}
		let newNode = normalizeElements(node)

		this._setOptions(newNode, options)

		return this.each((el) => {
			w(newNode).each((p, i) => {
				if (el === p) {
					return p
				}
				p.append(
					i === 0 ? el : el.cloneNode(true)
				)
			})
		})
	}
	/**
	 * Agrega un nuevo elemento al principio del padre seleccionado
	 * @memberOf Fascino
	 * @public
	 * @param  {(Element|Fascino|String)} node
	 * @param  {Object} options
	 * @return {Fascino}
	 */
	prepend(node, options) {
		if (this.length === 0) {
			return this
		}
		let newNode = normalizeElements(node)

		this._setOptions(newNode, options)

		return this.each((el, elIndex) => {
			w(newNode).each((e) => {
				if (el === e) return
				let child = elIndex === 0 ? e : e.cloneNode(true)
				script(child)
				if (child.tagName && child.tagName !== 'SCRIPT') {
					el.prepend(child)
				}
			})
		})
	}
	/**
	 * Agrega el elemento seleccionado al nuevo padre
	 * @memberOf Fascino
	 * @public
	 * @param  {(Element|Fascino)} node
	 * @param  {Object} options
	 * @return {Fascino}
	 */
	prependTo(node, options) {
		if (this.length === 0) {
			return this
		}
		let newNode = normalizeElements(node)

		this._setOptions(newNode, options)

		return this.each((el)=>{
			w(newNode).each((parent, parIndex) => {
				if (el === parent) return parent
				w(parent).prepend(parIndex === 0 ? el : el.cloneNode(true))
			})
		})
	}
	
	/**
	 * Clona el elemento seleccionado
	 * @memberOf Fascino
	 * @public
	 * @param  {Boolean} deep Verdadero si los hijos del nodo también deben ser clonados
	 * @return {Array}
	 */
	clone(deep = false) {
		let res = []
		if (not(deep)) {
			deep = false
		}

		this.each((e) => {
			let el = e.cloneNode(deep)
			res.push(el)
		})
		return merge(w().Elem, res)
	}
	/**
	 * Crea una copia de un nodo desde un documento externo
	 * @memberOf Fascino
	 * @public
	 * @param  {Boolean} deep Verdadero si los hijos del nodo también deben ser importados
	 * @return {Array}
	 */
	import(deep = true) {
		let res = []
		if (not(deep)) {
			deep = true
		}
		this.each((e) => {
			res.push(document.importNode(e, deep))
		})
		return merge(w().Elem, res)
	}
	/**
	 * Transfiere un node desde otro document al documento del método
	 * @memberOf Fascino
	 * @public
	 * @return {Array}
	 */
	adopt() {
		let res = []
		this.each((e) => {
			res.push(document.adoptNode(e))
		})
		return merge(w().Elem, res)
	}
	/**
	 * Obtiene el contenido de un Iframe o Template
	 * @memberOf Fascino
	 * @public
	 * @return {Array}
	 */
	contents() {
		if (this.length === 0) {
			return this
		}
		let res = []
		this.each((el) => {
			let content
			if (el.nodeName === 'IFRAME') {
				content = el.contentDocument || el.contentWindow.document
			} else if (el.nodeName === 'TEMPLATE') {
				content = el.content
			}
			res.push(content)
		})

		return merge(w().Elem, res)
	}
	/**
	 * Obtiene el o los padres de un elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Element)} sel Selector del padre a buscar
	 * @return {Fascino} Lista de Padres
	 */
	parents(sel) {
		const parents = []
		this.each((el) => {
			let ancestor = el.parentNode
			while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== NODETEXT) {
				if (ancestor.matches(sel)) {
					parents.push(ancestor)
				}

				ancestor = ancestor.parentNode
			}
		})

		return w(parents)
	}
	/**
	 * Obtiene el padre del elemento
	 * @memberOf Fascino
	 * @public
	 * @return {Fascino}
	 */
	parent() {
		const parent = []
		this.each((el) => {
			parent.push(el.parentNode)
		})
		return w(parent)
	}
	/**
	 * Obtiene o busca el hermano anterior
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Element)} sel
	 * @return {(Fascino|Array)}
	 */
	prev(sel) {
		let el = this.Elem[0],
			previous = el.previousElementSibling

		if (empty(sel)) {
			return w(previous)
		}

		while (previous) {
			if (previous.matches(sel)) {
				return w(previous)
			}

			previous = previous.previousElementSibling
		}

		return []
	}
	/**
	 * Obtiene o busca el hermano siguiente
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Element)}   sel
	 * @return {(Fascino|Array)}
	 */
	next(sel) {
		let el = this.Elem[0],
			next = el.nextElementSibling

		if (empty(sel)) {
			return w(next)
		}

		while (next) {
			if (next.matches(sel)) {
				return w(next)
			}

			next = next.nextElementSibling
		}

		return []
	}
	/**
	 * Busca el ascendiente más cercano al elemento seleccionado
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Element)} sel Selector del ascendiente a buscar
	 * @return {(Fascino|Element|Array|Null)}
	 */
	closest(sel) {
		if (this.length === 0) return this

		if (!sel) {
			return this.parent(sel)
		}

		const res = []
		this.each((el) => {
			if ('closest' in el) {
				res.push(el.closest(sel))
			} else {
				while (el) {
					if (!el) break
					if (el.matches(sel)) {
						res.push(el)
						return
					}
					el = el.parentElement
				}
			}
		})
		return res
	}
	/**
	 * Vacía el contenido HTML de un elemento
	 * @memberOf Fascino
	 * @public
	 * @return {Fascino}
	 */
	emptyHtml() {
		return this.each((el) => {
			el.innerHTML = ''
		})
	}
	/**
	 * Vacía el valor de un elemento
	 * @memberOf Fascino
	 * @public
	 * @return {Fascino}
	 */
	emptyVal() {
		return this.each((el) => {
			el.value = ''
		})
	}
	/**
	 * Encierra un elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {(Element|String)} el
	 * @return {Fascino} El nuevo padre
	 */
	wrap( el ) {
		if (this.length === 0) {
			return
		}

		const wrapper = w(normalizeElements(el))

		if (!wrapper.length) {
			return
		}

		let res = []

		this.each((el) => {
			let _target, _wrapper

			_wrapper = w(wrapper.clone(true))
			_wrapper.insertBefore(el)

			_target = _wrapper
			while (_target.children().length) {
				_target = _target.children().eq(0)
			}
			_target.append(el)

			res.push(_wrapper.eq(0))
		})

		return w(res)
	}
	/**
	 * Busca y encierra todos los elemento del tipo dado
	 * @memberOf Fascino
	 * @public
	 * @param  {(NodeList|Fascino-Object|Object|Array)} el
	 * @return {Fascino}
	 */
	wrapAll( el ) {
		let wrapper, _wrapper, _target

		if (this.length === 0) {
			return
		}

		wrapper = w(normalizeElements(el))

		if (!wrapper.length) {
			return
		}

		_wrapper = w(wrapper.clone(true))
		_wrapper.insertBefore(this.Elem[0])

		_target = _wrapper
		while (_target.children().length) {
			_target = _target.children().eq(0)
		}

		this.each((e) => {
			_target.append(e)
		})

		return _wrapper
	}
	/**
	 * Busca y encierra los hijo de un elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {(Elemento|Fascino|String)} el
	 * @return {Fascino}
	 */
	wrapInner( el ) {
		if (this.length === 0) {
			return
		}

		const wrapper = w(normalizeElements(el))

		if (!wrapper.length) {
			return
		}

		let res = []

		this.each((e) => {
			let elem = w(e),
				html = elem.html(),
				wrp = w(wrapper.clone(true))

			elem.html(wrp.html(html))
			res.push(wrp)
		})

		return w(res)
	}
	/**
	 * Desencierra los elemento
	 * @memberOf Fascino
	 * @public
	 * @return {Fascino}
	 */
	unwrap() {
		return this.each((el) => {
			let p = el.parentNode
			while (el.firstChild) {
				p.insertBefore(el.firstChild, el)
			}
			p.removeChild(el)
		})
	}
	/**
	 * Elimina uno o todos los elementos del DOM
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Element|Array)} sel Selector a eliminar
	 * @return {Array}
	 */
	remove(sel) {
		let out = !empty(sel) ?
				this.Elem.filter((el) => {
					return el.matches(sel)
				}) :
				this.Elem,
			res = []
		out.forEach((el) => {
			res.push(el.parentNode.removeChild(el))
		})

		return [].concat(...this.Elem, ...res)
	}
	/**
	 * Inserta un elemento antes del elemento seleccionado
	 * @memberOf Fascino
	 * @public
	 * @param  {Element} elements
	 * @return {Fascino}
	 */
	insertBefore(elements) {
		let _el = w(elements)
		return this.each((el) => {
			_el.each((_e, i) => {
				if (_e === el) {
					return
				}
				let p = _e.parentNode
				if (p) {
					p.insertBefore(
						i === 0 ? el : el.cloneNode(true),
						_e
					)
				}
			})
		})
	}
	/**
	 * Inserta un elemento después del elemento seleccionado
	 * @memberOf Fascino
	 * @public
	 * @param  {Element} elements
	 * @return {Fascino}
	 */
	insertAfter(elements) {
		let _el = w(elements)
		return this.each((el) => {
			_el.each((_e, i) => {
				if (_e === el) {
					return
				}
				let p = _e.parentNode
				if (p) {
					p.insertBefore(
						i === 0 ? el : el.cloneNode(true),
						_e.nextSibling
					)
				}
			})
		})
	}
	/**
	 * Agrega un elemento o etiquetas HTML después del elemento dado
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Element)} html
	 * @param  {String} position la posición equivale a afterbegin o afterend; @default afterbegin
	 * @return {Fascino}
	 */
	after(html, position = 'afterbegin') {
		return this.each((el) => {
			if (isString(html)) {
				el.insertAdjacentHTML(position, html)
			} else {
				w(html).insertAfter(el)
			}
		})
	}
	/**
	 * Agrega un elemento o HTML antes del elemento dado
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Element)} html
	 * @param  {String} position la posición equivale a beforebegin o beforeend; @default beforebegin
	 * @return {Fascino}
	 */
	before(html, position = 'beforebegin') {
		return this.each((el) => {
			if (isString(html)) {
				el.insertAdjacentHTML(position, html)
			} else {
				w(html).insertBefore(el)
			}
		})
	}
	/**
	 * Obtiene o Establece el texto al elemento seleccionado
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Null)} txt
	 * @return {(Fascino|String)}
	 */
	text(txt) {
		return empty(txt) ?
				this._prop('textContent') :
				this._prop('textContent', txt)
	}
	/**
	 * Obtiene o Establece el contenido HTML del elemento seleccionado
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Fascino|Element)} html
	 * @return {(Fascino|String)}
	 */
	html(html) {
		let value = []
		if (this.length === 0) {
			return this
		}

		if (html === '') {
			return this._prop('innerHTML', '')
		}

		if (empty(html)) {
			return this._prop('innerHTML')
		}

		if (Array.isArray(html)) {
			value = [].concat(...value, ...html)
		} else if (html instanceof Element || isFascinoElement(html)) {
			let res = [],
				h = (html instanceof Element) ? w(html) : html

			h.each((v) => {
				res.push(w(v).outerHTML())
			})

			value = [].concat(...value, ...res)
		} else {
			value.push(html)
		}

		this._prop('innerHTML',
			value.length === 1 && empty(value[0]) ? '' : value.join('\n')
		)

		return this
	}
	/**
	 * Obtiene o Establece el valor de un elemento dado
	 * @memberOf Fascino
	 * @public
	 * @param  {String} value Valor del input, textarea o elemento que contenta value
	 * @return {(Fascino|String)}
	 */
	val(value) {
		if (not(value)) {
			return this.length === 0 ? undefined : this._prop('value')
		}

		return this.each((el) => {
			if (typeof el.value != 'undefined') {
				el.value = value
			} else {
				w(el).html(value)
			}
		})
	}
	/**
	 * Obtiene el HTML o envoltura del elemento dado
	 * @memberOf Fascino
	 * @public
	 * @return {String}
	 */
	outerHTML() {
		return this._prop('outerHTML')
	}
	/**
	 * Agrega clases al elemento dado
	 * @memberOf Fascino
	 * @example
	 * _$(mi-elem).addClass('miclass')
	 * _$(mi-elem).addClass('miclass1', 'miclass2' /*...*\)
	 * @public
	 * @param {...String} arg Lista de clases separadas por coma(,)
	 * @return {Fascino}
	 */
	addClass(...arg) {
		return this.each((e) => {
			e.classList.add(...arg)
		})
	}
	/**
	 * Elimina Clases del elemento seleccionado
	 * @memberOf Fascino
	 * @public
	 * @param  {...String} args
	 * @return {Fascino}
	 */
	removeClass(...args) {
		return this.each((e) => {
			e.classList.remove(...args)
		})
	}
	/**
	 * Intercambia clases del elemento dado
	 * @memberOf Fascino
	 * @public
	 * @param  {...String} args Lista de Clases a cambiar
	 * @return {Fascino}
	 */
	toggleClass(...args) {
		return this.each((e) => {
			e.classList.toggle(...args)
		})
	}
	/**
	 * Reemplaza una clase por otra
	 * @memberOf Fascino
	 * @public
	 * @param  {...String} args Clase vieja clase nueva
	 * @example
	 * miElement.replaceClass('oldClass', 'NewClass')
	 * @return {Fascino}
	 */
	replaceClass(...args) {
		return this.each((e) => {
			e.classList.replace(...args)
		})
	}
	/**
	 * Verifica si el elemento posee una clase
	 * @memberOf Fascino
	 * @public
	 * @param  {String}  className Nombre de la clase
	 * @return {Boolean} Verdadero si existe
	 */
	hasClass(className) {
		return this.Elem[0].classList.contains(className)
	}
	/**
	 * Agrega u Obtiene estilos CSS a los Elementos
	 * @memberOf Fascino
	 * @public
	 * @param  {...(String|Array|Object)} arg
	 * @example <caption>Uso</caption>
	 * miElement.style('display', 'none') // establece la propiedad CSS a display none
	 * miElement.style({
	 *   border: '1px solid #ff0' // Establece un border Amarillo
	 *   color: '#000' // y un color de texto Negro
	 * })
	 * miElement.style('display') // retorna none
	 * miElement.style() // Retorna todos los Estilos establecidos(CSSStyleDeclaration)
	 * miElement.style([
	 *   'border', 'color' // Retorna un Objecto {IDfromMiElem: {border: '1px solid #ff0', color: '#000'}}
	 * ]) // Importante: si el elemento no tiene ID se le creara uno aleatorio
	 * @return {(Fascino|Array|Object|String)}
	 */
	style(...arg) {
		if (this.length === 0) {
			return this
		}

		if (arg.length === 0) {
			return getComputedStyle(this.Elem[0])
		}

		if (arg.length === 1) {
			if (isArrayish(arg[0])) {
				let getStyle = {}, i = 0
				this.each((el) => {
					let id = el.getAttribute('id'),
						prefix = !empty(id) ? id : el.tagName+i
					getStyle[prefix] = {}
					arg[0].forEach( (nameStyle) => {
						getStyle[prefix][nameStyle] = el.style[nameStyle]
					})
					i++
				})
				return getStyle
			} else if (isObject(arg[0])) {
				return this.each((el) => {
					for (let key in arg[0]) {
						if (hasProp(arg[0], key)) {
							let value = arg[0][key]
							el.style.setProperty(key, value)
						}
					}
				})
			} else if (arg[0].indexOf(':') === 0) {
				return getStyleComputed(this.Elem[0], null, arg[0])
			} else if (arg[0] === '*' || arg[0] === 'all') {
				return getStyleComputed(this.Elem[0])
			} else {
				let st = getStyleComputed(this.Elem[0])
				return arg[0] in st ? st[arg[0]] : ''
			}
		}

		if (arg.length === 2 || arg.length === 3) {
			return this.each((el) => {
				el.style.setProperty(...arg)
			})
		}

		return this
	}
	/**
	 * Remueve todos o los estilos establecidos
	 * @memberOf Fascino
	 * @public
	 * @example
	 * miElement.removeStyle('border', 'color') // Removerá los estilos del border y el color
	 * @param  {...String} name Lista de Stilo}
	 * @return {Fascino}
	 */
	removeStyle(...name) {
		if (empty(name) || this.length === 0) {
			return this
		}

		return this.each((el) => {
			name.forEach((n) => {
				el.style.removeProperty(n)
			})
		})
	}
	/**
	 * Obtiene o estable el valor de la barra de desplazamiento vertical
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Number)} val
	 * @return {(String|Number|Fascino)}
	 */
	scrollTop(val) {
		if (this.length === 0) {
			return this
		}

		if (not(val)) {
			return this.Elem[0] === window ? window.pageYOffset : this.Elem[0].scrollTop
		}

		return this.each((el) => {
			el.scrollTop = val
		})
	}
	/**
	 * Obtiene o estable el valor de la barra de desplazamiento Horizontal
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Number)} val
	 * @return {(String|Number|Fascino)}
	 */
	scrollLeft(val) {
		if (this.length === 0) {
			return this
		}

		if (empty(val)) {
			return this.Elem[0] === window ? window.pageXOffset : this.Elem[0].scrollLeft
		}

		return this.each((el) => {
			el.scrollLeft = val
		})
	}
	/**
	 * Obtiene o Establece el Ancho total del elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Number)} val
	 * @return {(String|Number|Fascino)}
	 */
	outerWidth(val) {
		return this._sizeOut('width', val)
	}
	/**
	 * Obtiene o Establece la Altura total del elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Number)} val
	 * @return {(String|Number|Fascino)}
	 */
	outerHeight(val) {
		return this._sizeOut('height', val)
	}
	/**
	 * Obtiene o Establece la posición del Elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {Object} val Objeto {top,left}
	 * @return {(Object|Fascino)}
	 */
	offset(val) {
		if (this.length === 0) {
			return this
		}

		if (empty(val)) {
			const rect = this.Elem[0].getBoundingClientRect(),
				scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
				scrollTop = window.pageYOffset || document.documentElement.scrollTop
			return {
				top: rect.top + scrollTop,
				left: rect.left + scrollLeft
			}
		}

		return this.each((el) => {
			let offset = w(el).offset(),
				top = val.top,
				left = val.left,
				position = getStyleComputed(el).position

			if (position === 'static') {
				w(el).style('position', 'relative')
			}

			if (['absolute', 'fixed'].indexOf(position) === -1) {
				top = top - offset.top
				left = left - offset.left
			}

			w(el).style({
				top: top,
				left: left
			})
		})
	}
	/**
	 * Obtiene la Posición del elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {Boolean} margin Verdader si se incluye el margen
	 * @return {(Object|Undefined)}
	 */
	position(margin = false) {
		if (this.length === 0) {
			return undefined
		}

		if (typeof margin != 'boolean') {
			try {
				margin = JSON.parse(margin)
			} catch (e) {
				margin = false
			}
		}

		let ml = 0, mt = 0

		if (margin) {
			let s = getStyleComputed(this.Elem[0])
			ml = parseInt(s['margin-left'])
			mt = parseInt(s['margin-top'])
		}

		return {
			top: this.Elem[0].offsetTop - mt,
			left: this.Elem[0].offsetLeft - ml
		}
	}
	/**
	 * Obtiene o Establece la posición horizontal del elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Number)}  v Nueva posición
	 * @param  {Boolean} m Si se debe incluir el margen
	 * @return {(String|Number|NaN)}
	 */
	left(v, m = false) {
		if (this.length === 0) {
			return NaN
		}
		if (typeof v === 'boolean') {
			m = v
			v = undefined
		}
		if (empty(v)) {
			return this.position(m).left
		}

		return this.style('left', !isNaN(v) ? v + 'px' : v)
	}
	/**
	 * Obtiene o Establece la posición vertical del elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Number)}  v Nueva posición
	 * @param  {Boolean} m Si se debe incluir el margen
	 * @return {(String|Number|NaN)}
	 */
	top(v, m = false) {
		if (this.length === 0) {
			return NaN
		}
		if (typeof v === 'boolean') {
			m = v
			v = undefined
		}
		if (empty(v)) {
			return this.position(m).top
		}

		return this.style('top', !isNaN(v) ? v + 'px' : v)
	}
	/**
	 * Obtiene el Ancho interno del elemento
	 * @memberOf Fascino
	 * @public
	 * @return {Number}
	 */
	innerWidth() {
		return this.Elem[0] === window ? window.innerWidth : this.Elem[0].clientWidth
	}
	/**
	 * Obtiene el Alto interno del elemento
	 * @memberOf Fascino
	 * @public
	 * @return {Number}
	 */
	innerHeight() {
		return this.Elem[0].clientHeight
	}
	/**
	 * Obtiene o establece la altura del elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {(Number|String)} val
	 * @return {(Number|String|Fascino)}
	 */
	height(val) {
		return this._size('height', val)
	}
	/**
	 * Obtiene o establece la anchura del elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {(Number|String)} val
	 * @return {(Number|String|Fascino)}
	 */
	width(val) {
		return this._size('width', val)
	}
	/**
	 * Oculta un ELemento y ejecuta la función dada
	 * @memberOf Fascino
	 * @public
	 * @param  {Function} callback
	 * @return {Fascino}
	 */
	hide(callback) {
		return this.each((el) => {
			let display = w(el).style('display'),
				opacity = w(el).style('opacity')

			if (display != 'none' && parseInt(opacity) != 0) {
				w(el).origin('display', display)
				w(el).origin('opacity', opacity)

				w(el).style({
					display: 'none',
					opacity: 0
				})
			}
			if (isFunction(callback)) {
				callback.call(this, el)
			}
		})
	}
	/**
	 * Muestra un elemento y ejecuta la función dada
	 * @memberOf Fascino
	 * @public
	 * @param  {Function} callback
	 * @return {Fascino}
	 */
	show(callback) {
		return this.each((el) => {
			let display = w(el).origin('display', undefined, 'block'),
				opacity = w(el).origin('opacity', undefined, '1'),
				setDisplay = 'block', setOpacity = 1

			if (display && display !== 'none') {
				setDisplay = display
			}
			if (opacity && opacity !== 0) {
				setOpacity = opacity
			}

			w(el).style({
				display: setDisplay,
				opacity: setOpacity
			})

			if (isFunction(callback)) {
				callback.call(this, el)
			}
		})
	}
	/**
	 * Muestra un elemento con desvanecimiento suave
	 * @memberOf Fascino
	 * @public
	 * @param  {Function} fn   Funcion a ejecutar despues del efecto
	 * @param  {Number}   time Tiempo del desvanecimiento
	 * @return {Fascino} 
	 */
	fadeIn(fn, time = 600) {
		this.style({
			opacity: 0,
			display: ''
		})

		if (isNumber(fn)) {
			time = fn 
			fn = (el) => {}
		}

		var last = +new Date(),
			element = this.Elem[0],
			view = () => {
				this.style('opacity', +element.style.opacity + (new Date() - last) / time)
				last = +new Date()
				
				if (+element.style.opacity < 1) {
					(window.requestAnimationFrame && requestAnimationFrame(view)) || setTimeout(view, 16)
				}
			}

		view()
		if (isFunction(fn)) {
			fn.apply(this, [element])
		}
		return this;
	}
	/**
	 * Funcion que Oculta con un desvanecimiento suave
	 * @memberOf Fascino
	 * @public
	 * @param  {Function} fn   Función a ejecutar luego de ocultar
	 * @param  {Number}   time Tiempo del desvanecimiento
	 * @return {Fascino}
	 */
	fadeOut(fn, time = 600) {
		this.style({
			opacity: 1,
			display: ''
		})
		var last = +new Date(),
			element = this.Elem[0],
			view = () => {
				element.style.opacity = Number(+element.style.opacity - (new Date() - last) / time).toFixed(4);
				last = +new Date()
				
				if (-element.style.opacity <= 0) {
				    (window.requestAnimationFrame && requestAnimationFrame(view)) || setTimeout(view, 16)
				}
			}
		view()
		if (isFunction(fn)) {
			fn.apply(this, [element])
		}
		return this;
	}
	/**
	 * Obtiene o Establece los Atributos de un elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {...(String|Array|Object|Function)} args
	 * @example <caption>Uso</caption>
	 * miElement.attr('name', 'paswd') // Establece el Atributo Name a passwd
	 * miElement.attr({
	 *   id:'miElementID', // Establece el Id a miElementID y cambia el placeholder
	 *   placeholder:'Escribe Aquí'
	 * })
	 * miElement.attr() // Re-establece los Atributos y retorna un NodeMap con ellos en caso de no poseer atributos retornara un Objecto Fascino
	 * miElement.attr('name') // Retorna 'passwd' o false
	 * miElement.attr(['name', 'id']) // Retorna un objecto {miElementID: {name: 'passwd', id:'miElementID'}}
	 * // Ademas podemos pasar una función que se invocara dentro de un bucle que recorre los atributos
	 * miElement.attr( function(attrName, attrValue, Attr) {
	 *   console.log(
	 *       this, // El Elemento iterado
	 *       attrName, // El nombre del atributo
	 *       attrValue, // El Valor del Attributo
	 *       Attr // Lista de todos los atributos
	 * )
	 * })
	 * @return {(String|Array|Object|Fascino)}
	 */
	attr(...args) {
		if (this.length === 0) {
			return this
		}
		if (args.length === 0) {
			if (this.Elem[0].hasAttributes()) {
				const Attr = this.Elem[0].attributes
				Array.from(Attr).forEach((a)=>{
					this.Elem[0].setAttribute(a.nodeName, a.nodeValue)
				})
				return Attr
			} else {
				return this
			}
		}
		if (args.length === 1) {
			if (isArrayish(args[0])) {
				const Attrs ={}
				let i = 0
				this.each((el) => {
					let id = el.getAttribute('id'),
						prefix = !not(id) ? id : el.tagName+i
					Attrs[prefix] = {}
					args[0].forEach((a) => {
						if (el.hasAttributes(a)) {
							Attrs[prefix][a] = el.getAttribute(a)
						} else {
							Attrs[prefix][a] = false
						}
					})
					i++
				})
				return Attrs
			} else if (isObject(args[0])) {
				return this.each((el) => {
					for (let key in args[0]) {
						if (hasProp(args[0], key)) {
							let value = normalizeData(args[0][key])
							if (key in el) {
								el[key] = value
							} else {
								el.setAttribute(key, value)
							}
						}
					}
				})
			} else if (isString(args[0])) {
				return this.Elem[0].hasAttributes(args[0]) ? this.Elem[0].getAttribute(args[0]) : false
			} else if (isFunction(args[0])) {
				return this.each((el) => {
					if (el.hasAttributes()) {
						let a = el.attributes
						Array.from(a).forEach((attr) => {
							args[0].call(el, [attr.nodeName, attr.nodeValue, attr])
						})
					}
				})
			}
		}

		return this.each((el) => {
			let key = args[0],
				value = normalizeData(args[1])
			if (key in el) {
				el[key] = value
			} else {
				el.setAttribute(key, value)
			}
		})
	}
	/**
	 * Remueve los atributos dados
	 * @memberOf Fascino
	 * @public
	 * @param  {...String} args Lista de Atributos
	 * @example
	 * miElement.removeAttr('style', 'name')
	 * @return {Fascino}
	 */
	removeAttr(...args) {
		return this.each((el) => {
			let attrs = []
			if (args.length == 1 && !isArrayish(args[0])) {
				attrs.push(args[0])
			} else {
				attrs = [].slice(args)
			}

			attrs.forEach((a)=>{
				el.removeAttribute(a)
			})
		})
	}
	/**
	 * Verifica si el elemento tiene el atributo dado
	 * @memberOf Fascino
	 * @public
	 * @param  {String}  attr
	 * @return {Boolean}
	 */
	hasAttr(attr) {
		if (empty(attr)) {
			return false
		}

		return this.Elem[0].hasAttribute(attr)
	}
	/**
	 * Alterna los Atributos y su valor
	 * @memberOf Fascino
	 * @public
	 * @param  {String} name  Nombre del Atributo
	 * @param  {String} value Valor d el Atributo
	 * @return {Fascino}
	 */
	toggleAttr(name, value = undefined) {
		let _this = this
		return this.each((el) => {
			if (name in el && el[name] !== value) {
				el[name] = value
			}
			if (!empty(name) && not(value)) {
				if (el.hasAttributes(name)) {
					el.removeAttribute(name)
				}
			} else {
				_this.Elem = [el]
				_this.attr(name, value)
			}
		})
	}
	/**
	 * Elimina todos los atributos de un elemento
	 * @memberOf Fascino
	 * @public
	 * @return {Fascino}
	 */
	cleanAttr() {
		return this.each((el) => {
			if (el.hasAttributes()) {
				let attr = el.attributes
				attr.forEach((a) => {
					el.removeAttribute(a.nodeName)
				})
			}
		})
	}

	/**
	 * Establece u Obtiene los datos del Elemento Dataset
	 * @memberOf Fascino
	 * @public
	 * @example <caption>Uso</caption>
	 * miElement.data('role', 'dialog')  // Establece el Rol a dialog
	 * // Esteble al data-json al objecto dado
	 * miElement.data({
	 *   json:{
	 *      a: 1,
	 *      b: 2
	 * }
	 * })
	 * miElement.data() // Retorna un Objecto con todos los datos del Atributo data
	 * miElement.data('role') // Retorna Dialog
	 * miElement.data(['role','json']) // Retornara un Objecto {miElementID: {role: 'dialog', json: {a:1,b:2}}}
	 * @param  {...(String|Array|Object)} args
	 * @return {(String|Array|Object|Fascino)}
	 */
	data(...args){
		return this.length === 0 ? this : this._ds.data(this.Elem, ...args)
	}
	/**
	 * Remueve los Atributos data
	 * @memberOf Fascino
	 * @public
	 * @param  {...String} keys Lista de nombres de data sin el data
	 * @example
	 * // <input id="miElement" data-role='pick' data-color="#fff">
	 * _$('#miElement').removeData('role', 'color');
	 * // Obtendremos
	 * // <input id="miElement">
	 * @return {Fascino}
	 */
	removeData(...keys) {
		return this.each((el) => {
			this._ds.remove(el, ...keys)
		})
	}
	/**
	 * Verifica si el Elemento tiene un atributo data dado
	 * @memberOf Fascino
	 * @public
	 * @param  {String}  key Nombre sin el data
	 * @return {Boolean}
	 */
	hasData(key) {
		if (empty(key)) {
			return false
		}

		if (this._ds.has(this.Elem[0], key)) {
			return this._ds.get(this.Elem[0], key)
		}

		if (hasProp(this.Elem[0].dataset, key)) {
			return true
		}

		return this.hasAttr(`data-${key}`)
	}
	/**
	 * Alterna entre los atributos data
	 * @memberOf Fascino
	 * @public
	 * @param  {String} name  Nombre del Atributo sin el data
	 * @param  {(String|Object|Array)} value Valor del atributo data
	 * @return {(Boolean|String|Object|Array|Fascino)}
	 */
	toggleData(name, value) {
		if (arguments.length === 0) {
			return false
		}

		if (this.hasData()) {
			return this._ds.access(this.ELem[0], name, value)
		}

		return this.toggleAttr(`data-${name}`, value)
	}
	/**
	 * Establece el Evento para un elemento<br>
	 * Si va usar eventos estándar es mejor que use los de la lista <code>_$().click(), $().blur() ...</code>
	 * @memberOf Fascino
	 * @public
	 * @param  {(String|Array)} eventsList El nombre del Evento
	 * @param  {String} sel Namespace o selector
	 * @param  {Function} handler Función a ejecutar
	 * @param  {Object} options Opciones de AddEventListiner
	 * @return {Fascino}
	 */
	on(eventsList, sel, handler, options) {
		return this.each((el) => {
			this._ev.on(el, eventsList, sel, handler, options)
		})
	}
	/**
	 * Desvincula el Evento para un elemento
	 * @memberOf Fascino
	 * @public
	 * @param  {String} eventsList El nombre del Evento
	 * @param  {String} sel Namespace o selector
	 * @param  {Function} handler Función a ejecutar
	 * @param  {Object} options Opciones de AddEventListiner
	 * @param  {Number} ix Index del evento
	 * @return {Fascino}
	 */
	off(eventsList, sel, options, ix) {
		return this.each((el) => {
			this._ev.off(el, eventsList, sel, options, ix)
		})
	}
	/**
	 * Ejecuta el evento solo para el primer evento dado
	 * @memberOf Fascino
	 * @param  {String} events  El evento
	 * @param  {String} sel Namespace o selector
	 * @param  {Function} handler Función a ejecutar
	 * @param  {Object} options Opciones de AddEventListiner
	 * @return {Fascino}
	 */
	one(events, sel, handler, options) {
		return this.each((el) => {
			this._ev.one(el, events, sel, handler, options)
		})
	}
	/**
	 * Dispara o Crea un Evento Personalizado
	 * @memberOf Fascino
	 * @public
	 * @param  {String} name Nombre del Evento
	 * @param  {Object} data Información del Evento
	 * @return {(void|Fascino)}
	 */
	fire(name, data) {
		var _name = normName(name), newEv

		if(['submit', 'reset'].indexOf(_name) > -1){
			if(this.Elem[0].nodeName === 'FORM'){
				this.Elem[0][_name].call(this.Elem[0])
			} else {
				let form = this.parents('form')
				form.Elem[0][_name].call(form.Elem[0])
			}
			return this
		}

		if (ListEvents.indexOf(_name) > -1) {
			this.Elem[0][_name].call(this.Elem[0])
		}

		newEv = this._ev.createEv(name, data)

		return this.each((el) => {
			let customEvent = w(el).data('customEvent'),
				et = {}

			et[_name] = newEv
			if(not(customEvent)) {
				w(el).data('customEvent', et)
			} else {
				w(el).data('customEvent', extend({}, customEvent, et))
			}
			this._ev.fire(el, newEv)
		})
	}
	/**
	 * Dispara un evento
	 * @memberOf Fascino
	 * @public
	 * @param  {String} name Nombre del Evento
	 * @param  {Object} data Información del evento
	 * @return {Fascino}
	 */
	trigger(name, data) {
		var _name = normName(name)
		return this.each((el) => {
			if (ListEvents.indexOf(_name) > -1) {
				el[_name].call(el)
			} else if(w(el).hasData('customEvent') ){
				let customEvent = w(el).data('customEvent')
				if(!not(customEvent) && hasProp(customEvent, _name)){
					this._ev.fire(el, customEvent[_name])
				} else {
					w(el).fire(name, data)
				}
			}
		})
	}
	/**
	 * Crea el evento hover
	 * @memberOf Fascino
	 * @public
	 * @param  {Function} fnOver Función de entrada
	 * @param  {Function} fnOut  Función de Salida
	 * @return {Fascino}
	 */
	hover(fnOver, fnOut) {
		return this.each((el) => {
			this.on('mouseenter', fnOver).on('mouseleave', fnOut)
		})
	}
	/**
	 * Obtiene las lista de eventos asignados aun elemento, si no se pasa ningun argumento se obtendran todos los eventos
	 * @param  {String} name  Nombre del Evento
	 * @param  {Number} index Posición del evento a buscar
	 * @return {Object}
	 */
	getEvent(name, index) {
		return this._ev.getEvents(this.Elem[0], name, index)
	}
}

[
	/**
	 * Obtiene o Establece el relleno del Elemento dado
	 * @memberOf Fascino
	 * @public
	 * @function padding
	 * @param  {(String|Number|null)} val   Valor del Elemento a establecer
	 * @param  {String} pseudo      Important
	 * @return {(Object|Fascino)}
	 */
	'padding', 
	/**
	 * Obtiene o Establece el relleno del Elemento dado
	 * @memberOf Fascino
	 * @public
	 * @function margin
	 * @param  {(String|Number|null)} val   Valor del Elemento a establecer
	 * @param  {String} pseudo      Important
	 * @return {(Object|Fascino)}
	 */
	'margin', 
	/**
	 * Obtiene o Establece el relleno del Elemento dado
	 * @memberOf Fascino
	 * @public
	 * @function border
	 * @param  {(String|Number|null)} val   Valor del Elemento a establecer
	 * @param  {String} pseudo      Important
	 * @return {(Object|Fascino)}
	 */
	'border'
].forEach((name) => {
	Fascino.addFn(name, function(val, pseudo){
		if (this.length === 0) {
			return
		}
		if (isString(val)) {
			if (val.indexOf(':') === 0) {
				pseudo = val
				val = undefined
			}
		}

		if (empty(val)) {
			let s = getStyleComputed(this.Elem[0], null, !empty(pseudo) ? pseudo : ''),
				res = {}
			if (name !== 'border') {
				res = {
					top: parseInt(s[`${name}-top`]),
					right: parseInt(s[`${name}-right`]),
					bottom: parseInt(s[`${name}-bottom`]),
					left: parseInt(s[`${name}-left`]),
				}
			} else {
				res = {
					top: parseInt(s['border-top-width']),
					right: parseInt(s['border-right-width']),
					bottom: parseInt(s['border-bottom-width']),
					left: parseInt(s['border-left-width'])
				}
			}
			return res
		}

		return this.each((el) => {
			if (isArrayish(val)) {
				w(el).style(name, val.join(' '))
			} else if (isObject(val)) {
				let res = {},
					ext = name === 'border' ? '-width' : ''
				for (let i in val) {
					if (hasProp(val, i)) {
						res[`${name}-${i}${ext}`] = val[i]
					}
				}
				w(el).style(res)
			} else {
				w(el).style(name, val)
			}
		})
	})
})

/**
 * Eventos Nativos de Javascript <br>
 * 'blur', 'focus', 'resize', 'scroll', 'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
 * 'mouseover', 'mouseout', 'mouseenter', 'mouseleave', 'change', 'select', 'submit', 'keydown',
 * 'keypress', 'keyup', 'contextmenu', 'touchstart', 'touchend', 'touchmove', 'touchcancel'
 * @example <caption>Uso</caption>
 * // Formato
 * // _$(miselector).[nameEvento](selector, function, options)
 * _$('body').clicK(function(e){
 * 	console.log("Presionastes sobre el body")
 * })
 * // Al hacer doble click sobre una section del elemento main, se ejecuta la funcion del click del body
 * _$('main').dblclick("section", function(e){
 * 		_$('body').click()
 * })
 * @memberOf Fascino
 * @public
 * @param  {String} s selector
 * @param  {Function} f Función a ejecutar
 * @param  {Object} o Opciones de AddEventListiner
 * @return {Fascino}
 */
ListEvents.forEach((n) => {
	Fascino.addFn(n, function(s, f, o) {
		return arguments.length > 0 ? this.on(n, s, f, o) : this.fire(n, {detail: 'Fire '+n})
	})
})