UNPKG

@gravityforms/utils

Version:
142 lines (128 loc) 4.12 kB
const DOCUMENT_NODE_TYPE = 9; if ( typeof Element !== 'undefined' && ! Element.prototype.matches ) { const proto = Element.prototype; proto.matches = proto.matchesSelector || proto.mozMatchesSelector || proto.msMatchesSelector || proto.oMatchesSelector || proto.webkitMatchesSelector; } /** * @ignore */ function closest( element, selector ) { while ( element && element.nodeType !== DOCUMENT_NODE_TYPE ) { if ( typeof element.matches === 'function' && element.matches( selector ) ) { return element; } element = element.parentNode; } } /** * @ignore */ function _delegate( element, selector, type, callback, useCapture ) { const listenerFn = listener.apply( this, arguments ); element.addEventListener( type, listenerFn, useCapture ); return { destroy() { element.removeEventListener( type, listenerFn, useCapture ); }, }; } /** * @module delegate * @description Delegates events to a selector. * * @since 1.0.0 * * @param {string|Array|NodeList|HTMLElement} elements A selector string, or element/array of elements. * @param {string} selector the selector string to search for and delegate. * @param {string|Function} type The event type or a function to execute. * @param {Function} callback The callback function to execute. * @param {boolean} useCapture Whether to use useCapture or not. https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#usecapture * * @return {unknown[]|{destroy(): void}|*} Destroy method is returned on instance. * * @example * import { delegate } from "@gravityforms/utils"; * * // With the default base (document) * * delegate( '.btn', 'click', function( e ) { * console.log( e.delegateTarget ); * }, false ); * * // With an element as base * * delegate( document.body, '.btn', 'click', function( e ) { * console.log( e.delegateTarget ); * }, false ); * * // With a selector (of existing elements) as base * * delegate( '.container', '.btn', 'click', function( e ) { * console.log( e.delegateTarget ); * }, false ); * * // With an array/array-like of elements as base * * delegate( document.querySelectorAll( '.container' ), '.btn', 'click', function( e ) { * console.log( e.delegateTarget ); * }, false ); * * // Remove delegation * * // With a single base element (default or specified) * * const delegation = delegate( document.body, '.btn', 'click', function( e ) { * console.log( e.delegateTarget ); * }, false ); * * delegation.destroy(); * * // With multiple elements (via selector or array) * // Note: selectors are always treated as multiple elements, even if one or none are matched. delegate() will return an array. * * const delegations = delegate( '.container', '.btn', 'click', function( e ) { * console.log( e.delegateTarget ); * }, false ); * * delegations.forEach( function( delegation ) { * delegation.destroy(); * } ); * */ function delegate( elements, selector, type, callback, useCapture = false ) { // Handle the regular Element usage if ( typeof elements.addEventListener === 'function' ) { return _delegate.apply( null, arguments ); } // Handle Element-less usage, it defaults to global delegation if ( typeof type === 'function' ) { // Use `document` as the first parameter, then apply arguments // This is a short way to .unshift `arguments` without running into deoptimizations return _delegate.bind( null, document ).apply( null, arguments ); } // Handle Selector-based usage if ( typeof elements === 'string' ) { elements = document.querySelectorAll( elements ); } // Handle Array-like based usage return Array.prototype.map.call( elements, function( element ) { return _delegate( element, selector, type, callback, useCapture ); } ); } /** * @ignore */ function listener( element, selector, type, callback ) { return function( e ) { e.delegateTarget = closest( e.target, selector ); if ( e.delegateTarget ) { callback.call( element, e ); } }; } export default delegate;