sveltecustom-events

Is it possible to dispatch a svelte custom event with a target object?


Is it possible to dispatch a svelte event (created with createEventDispatcher) with a target object like a native browser event?

I.e. receiving on the handler side event.target.value instead of event.detail.


Solution

  • Yes, it's possible but involve some hacking.

    You can take a look into the svelte source code to see how event dispatching works. I'll outline the key parts as below.

    Let's say we have two components, Inner and Outer.

    <!-- Inner.svelte -->
    <script>
    import { createEventDispatcher } from 'svelte'
    const dispatch = createEventDispatcher()
    const dispatchFoo = () => dispatch('foo', 'bar')
    </script>
    
    <button on:click={dispatchFoo}>send</button>
    
    <!-- Outer.svelte -->
    <script>
    import Inner from './Inner.svelte'
    const handleFoo = (e) => { console.log('receive foo', e) }
    </script>
    
    <Inner on:foo={handleFoo}></Inner>
    

    If you think about it, the event handler handleFoo is created in Outer, but it's actually register on Inner.

    Check the compiled JS code you'll see:

    1. inner.$$.callbacks["foo"] holds the event handler [src]
    2. when you click the button and call dispatch, it just read the inner.$$.callbacks["foo"] for potential handlers, and if found, call them with CustomEvent as argument [src]

    The only way to set customEvent.target is by dispatch that custom event using element.dispatchEvent(customEvent). But element.dispatchEvent is not used through the whole process.

    Solution (hack)

    Write your own createEventDispatcher.

    import { get_current_component } from 'svelte/internal'
    
    function createEventDispatcher() {
      const component = get_current_component();
      return (type, target, detail) => {
        const callbacks = component.$$.callbacks[type];
        if (callbacks) {
          const event = new CustomEvent(type, { detail });
          // the key is to call `dispatchEvent` manually to set `event.target`
          target.dispatchEvent(event);
          callbacks.slice().forEach((fn) => {
            fn.call(component, event);
          });
        }
      };
    }
    

    Svelte REPL