define(function () { "use strict"; class notjQuery { /** * Create a new notjQuery object * * @param {Element} element */ constructor(element) { if (! element) { throw new Error("Can't create a notjQuery object for `" + element + "`"); } this.element = element; } /** * Add an event listener to the element * * @param {string} type * @param {string} selector * @param {function} handler * @param {object} context */ on(type, selector, handler, context = null) { if (typeof selector === 'function') { context = handler; handler = selector; selector = null; } if (selector === null) { this.element.addEventListener(type, e => { if (type === 'focusin' && e.target.receivesCustomFocus) { // Ignore native focus event if a custom one follows if (e instanceof FocusEvent) { delete e.target.receivesCustomFocus; e.stopImmediatePropagation(); return; } } if (context === null) { handler.apply(e.currentTarget, [e]); } else { handler.apply(context, [e]); } }); } else { this.element.addEventListener(type, e => { if (type === 'focusin' && e.target.receivesCustomFocus) { // Ignore native focus event if a custom one follows if (e instanceof FocusEvent) { delete e.target.receivesCustomFocus; e.stopImmediatePropagation(); return; } } Object.defineProperty(e, 'currentTarget', { value: e.currentTarget, writable: true }); let currentParent = e.currentTarget.parentNode; for (let target = e.target; target && target !== currentParent; target = target.parentNode) { if (target.matches(selector)) { e.currentTarget = target; if (context === null) { handler.apply(target, [e]); } else { handler.apply(context, [e]); } break; } } }, false); } } /** * Trigger a custom event on the element, asynchronously * * The event will bubble and is not cancelable. * * @param {string} type * @param {{}} detail */ trigger(type, detail = null) { setTimeout(() => { this.element.dispatchEvent(new CustomEvent(type, { cancelable: true, // TODO: this should depend on whether it's a native or custom event bubbles: true, detail: detail })); }, 0); } /** * Focus the element * * Any other option than `preventScroll` is used as `event.detail`. * * @param {{}} options */ focus(options = {}) { let { preventScroll = false, ...data } = options; const hasData = Object.keys(data).length > 0; if (hasData) { this.element.receivesCustomFocus = true; } // Put separately on the event loop because focus() forces layout. setTimeout(() => this.element.focus({ preventScroll: preventScroll }), 0); if (hasData) { this.trigger('focusin', data); } } /** * Render the element string as DOM Element * * @param {string} html * @return {Element} */ static render(html) { if (typeof html !== 'string') { throw new Error("Can\'t render `" + html + "`"); } let template = document.createElement('template'); template.innerHTML = html; return template.content.firstChild; } } /** * Return a notjQuery object for the given element * * @param {Element} element * @return {notjQuery} */ let factory = function (element) { return new notjQuery(element); } // Define the static methods on the factory for (let name of Object.getOwnPropertyNames(notjQuery)) { if (['length', 'prototype', 'name'].includes(name)) { continue; } Object.defineProperty(factory, name, { value: notjQuery[name] }); } return factory; });