Skip to content

feat: add support of handleEvent object listener #15856

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

7nik
Copy link
Contributor

@7nik 7nik commented Apr 30, 2025

Closes #8196

The type of on:event on components is still function only, though it works with the object handler. Not sure where to fix it.

In the following case, it can be considered kind of a breaking change:

const props: HTMLAttributes = $props();
// later
props.onclick(); // will error when `onclick` is an object handler

someFnAcceptingCallback(props.onclick); // same problem

and any other place where the event handler is expected to be function only.

Also, the handler object seems to never get hoisted.

Before submitting the PR, please make sure you do the following

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • Prefix your PR title with feat:, fix:, chore:, or docs:.
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.
  • If this PR changes code within packages/svelte/src, add a changeset (npx changeset).

Tests and linting

  • Run the tests with pnpm test and lint the project with pnpm lint

Copy link

changeset-bot bot commented Apr 30, 2025

🦋 Changeset detected

Latest commit: c5b92b1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

Playground

pnpm add https://pkg.pr.new/svelte@15856

@ottomated
Copy link
Contributor

Is this worth adding? It seems like it's a non-zero amount of complexity and overhead for a basically useless feature, but if there's a legitimate use for handleEvent objects I suppose it could make sense to match the DOM spec

@Ocean-OS
Copy link
Contributor

Ocean-OS commented May 4, 2025

Is this worth adding? It seems like it's a non-zero amount of complexity and overhead for a basically useless feature, but if there's a legitimate use for handleEvent objects I suppose it could make sense to match the DOM spec

I've been using this feature for a little while and have found it quite useful. Here's the article through which I found out about it, it's pretty interesting

@svelte-docs-bot
Copy link

@7nik
Copy link
Contributor Author

7nik commented May 11, 2025

Oh, somehow I've missed that this should be the object but not the element.

@Rich-Harris
Copy link
Member

I'm also unclear on the real world benefit of this, can someone ELI5? I'm familiar with Andrea's article and I can't quite wrap my head around where it would be useful (though I do find myself wondering if we could take advantage of this approach internally instead of using delegation)

@Ocean-OS
Copy link
Contributor

I'm also unclear on the real world benefit of this, can someone ELI5?

  1. If you have an event handler(s) that you want to remove, it makes it a lot easier
  2. A way to unify all event handlers that could be related to each other
  3. Devs coming from vanilla JS might want to use it

though I do find myself wondering if we could take advantage of this approach internally instead of using delegation

I was actually thinking of making a PR that would do that, should I start working on it?

@Rich-Harris
Copy link
Member

  1. If you have an event handler(s) that you want to remove, it makes it a lot easier

How so? Is handler.handleEvent = null easier than handler = null?

  1. A way to unify all event handlers that could be related to each other

Is it easier than doing this?

<script>
  const handlers = {
    onmouseenter() {
      console.log('enter');
    },
    onmouseleave() {
      console.log('leave');
    }
  };
</script>

<div {...handlers}></div>

What would it look like — this?

<script>
  const handler = {
    handleEvent(e) {
      this['on' + e.type]?.(e);
    },
    onmouseenter() {
      console.log('enter');
    },
    onmouseleave() {
      console.log('leave');
    }
  };
</script>

<div onmouseenter={handler} onmouseleave={handler}></div>

It doesn't seem like an improvement. Maybe I'm missing something but I'm struggling to come up with a scenario where it's helpful.

  1. Devs coming from vanilla JS might want to use it

Aside from #8196 no-one has ever asked for it, so it feels like YAGNI to me.

I was actually thinking of making a PR that would do that, should I start working on it?

As long as you're at peace with the possibility that it wouldn't get merged 😅 it would have to have the same-or-better performance/memory characteristics, and not having any breaking changes in terms of timing etc. (It's possible that we'd actually be able to improve the timing situation, since handlers manually added with addEventListener would presumably no longer run in a distinct phase, but if that's considered a breaking change then this might have to wait until Svelte 6.)

@7nik
Copy link
Contributor Author

7nik commented May 14, 2025

I just looked at Solid's implementation and they also allow passing listener options on the object. Interesting idea. Though, they did it mostly to support adding listeners with the capture/passive/once options declaratively in the markup.

@7nik
Copy link
Contributor Author

7nik commented May 14, 2025

I did it just because the issue has the PRs welcome label 😄

@JonathonRP
Copy link

JonathonRP commented May 31, 2025

It would be cool if this was supported

<script>
  const handler = {
    handleEvent(e) {
      this['on' + e.type]?.(e);
    },
    onmouseenter() {
      console.log('enter');
    },
    onmouseleave() {
      console.log('leave');
    }
  };
</script>

<div {...handler}></div>

By

element.addEventListener(event, handler);
// Repeat for all events...

@7nik
Copy link
Contributor Author

7nik commented May 31, 2025

But that way you add the event listeners but not the handleEvent. It should be rather

<div onmouseenter={handler} onmouseleave={handler}>

So, my opinion:

  • Saving RAM - the era of "640kb RAM enough" was 4 decades ago, overhead of lambdas or bound function is barely noticeable. If RAM efficient code is needed - probably more light framework/library should be used or even vanilla JS. Also, Svelte already optimizes handlers by hoisting (though not in loops) - see the output.
  • Single entry for all event handlers - it's convent for the developer, but not the end user as they have to add the handler to each event, somehow know the list of events and do not forget any of them. Using a dedicated object with listeners or attachment is more convenient for the end user, although it's more boilerplate, REPL:
<script>
  import { on } from "svelte/events"
  class MyItem {
    handlers = {
      onmouseenter: (ev) => this.onmouseenter(ev),
      onmouseleave: (ev) => this.onmouseleave(ev), 
    }
    addHandlers = (node) => {
      const offs = [
        on(node, "mouseenter", (ev) => this.onmouseenter(ev)),
        on(node, "mouseenter", (ev) => this.onmouseenter(ev)),
      ];
      return () => offs.forEach(off => off());
    }
    test = 42
    onmouseenter() {
      console.log('enter', this.test);
    }
    onmouseleave() {
      console.log('leave', this.test);
    }
  }
  let item = new MyItem();
</script>

<div {...item.handlers}>spreading</div>
<div {@attach item.addHandlers}>attachment</div>
  • Always possible to remove a listener - Svelte does it for you, so it doesn't make sense here.
  • this refers to the object, but not the element - probably the only real advantage.
  • There is an interesting idea of passing listeners flags onclick={{ handleEvent() {...}, once: true, capture: true, passive: true }}, but:
    • once - can be easily worked around;
    • capture - supported as oneventnamecapture;
    • passive - nobody has asked for it yet.

Plus some disadvantages:

  • It can cause some typing troubles because now the listeners can be an object.
  • handleEvent is too little-known, unless we will advertise it in the docs and tutorial.

So, I tend to think that there is no real need for this feature. If a good use case appears, it can be re-opened.

@JonathonRP
Copy link

But that way you add the event listeners but not the handleEvent. It should be rather

<div onmouseenter={handler} onmouseleave={handler}>

I meant that spread of object with handler could be handled by compiler compilation to the syntax after "by"...

This feature would be nice to have for a lib I'm working on...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

addEventListener, object with a handleEvent() method support.
5 participants