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;