JSFiddle - React, Tailwind, and code Playground

HTML

<p>This event model adds 3 properties to <code>options</code> argument of the native model.</p>
<ol>
  <li><code>context</code>: binds <code>this</code> to the handler function</li>
  <li><code>delegatee</code> passes a selector for event delegation</li>
  <li><code>data</code> adds <code>data</code> property to <code>event</code> object</li>
</ol>
<p><strong>The model is a heavily simplified example only, don't use it in production.</strong></p>

<hr>

<h2>Testing section</h2>

<button id="but">Button</button>

CSS

code {
  font-size: 1.25em;
  color: red;
  font-weight: bold;
}

JavaScript

const events = {
	// Keeps track of the attached events
	tracked: new WeakMap(),
  // Attaches an event
	attach (target, type, handler, options = {}) {
  	const context = options.context || target,
    	capture = !!options.capture || false,
      once = !!options.once || false,
      passive = !!options.passive || true,
      delegatee = (typeof options.delegatee === 'string')
      	? options.delegatee : null,
      // The actual event listener
  		fn = e => {
      	if (options.hasOwnProperty('data')) {
        	// Add data property to event object
        	e.data = options.data;
        }
        if (delegatee) {
        	// Delegated event, quit if the delegatee doesn't exist
        	if (!e.target.closest(delegatee)) {return;}
        }
        // Call the dev defined handler function
    		handler.call(context, e);
        // Untrack a one-off event
        if (once) {
        	// Untrack a one-off event
        	this.detach(target, type, handler, capture);
        }
    	};
    target.addEventListener(type, fn, {capture, once, passive});
    if (!this.tracked.has(target)) {
    	this.tracked.set(target, []);
    }
    this.tracked.get(target).push({type, fn, handler, capture});
  },
  // Detaches an event
  detach (target, type, handler, capture = false) {
  	const events = this.tracked.get(target);
    if (!events) {return;} // Quit, target not found
    const eIndex = events.findIndex(e => 
    	e.type === type &&
      e.handler === handler &&
      e.capture === capture
    );
    if (eIndex < 0) {return;} // Quit, event was not found
    const event = events[eIndex];
    target.removeEventListener(event.type, event.fn, event.capture);
    events.splice(eIndex, 1);
    if (events.length < 1) {
    	// No events left on this element, remove it from the map
      this.tracked.delete(target);
    }
  }
}

// Testing ...

const but = document.querySelector('button'),
	ctxObject = {
  	prop: 'Context object'
  },
  handler = function (e) {
 ...