Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/spicy-peas-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"svelte": patch
---

feat: add svelte/events package and export attach function
4 changes: 4 additions & 0 deletions packages/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@
"./transition": {
"types": "./types/index.d.ts",
"default": "./src/transition/index.js"
},
"./events": {
"types": "./types/index.d.ts",
"default": "./src/events/index.js"
}
},
"repository": {
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/scripts/generate-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ await createBundle({
[`${pkg.name}/server`]: `${dir}/src/server/index.js`,
[`${pkg.name}/store`]: `${dir}/src/store/public.d.ts`,
[`${pkg.name}/transition`]: `${dir}/src/transition/public.d.ts`,
[`${pkg.name}/events`]: `${dir}/src/events/index.js`,
// TODO remove in Svelte 6
[`${pkg.name}/types/compiler/preprocess`]: `${dir}/src/compiler/preprocess/legacy-public.d.ts`,
[`${pkg.name}/types/compiler/interfaces`]: `${dir}/src/compiler/types/legacy-interfaces.d.ts`
Expand Down
1 change: 1 addition & 0 deletions packages/svelte/src/events/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { attach } from '../internal/client/dom/elements/events';
18 changes: 18 additions & 0 deletions packages/svelte/src/internal/client/dom/elements/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ export function create_event(event_name, dom, handler, options) {
return target_handler;
}

/**
* Attaches a DOM event handler to an element and returns a function that detaches the event. The event handler
* will be processed through Svelte's internal event delegation system and is the preferred way to imperatively
* attach event handlers instead of using `addEventListener`.
*
* @param {Element} dom
Comment thread
trueadm marked this conversation as resolved.
Outdated
* @param {string} event_name
* @param {EventListener} handler
* @param {AddEventListenerOptions} [options]
*/
export function attach(dom, event_name, handler, options = {}) {
var target_handler = create_event(event_name, dom, handler, options);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create_event calls addEventListener, it's not using event delegation. What am I missing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It uses the target function which ensures any delegated events run as expected, whilst still attaching the event to the target manually.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. The documentation is a bit misleading in that case, will rewrite it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.


return () => {
dom.removeEventListener(event_name, target_handler, options);
};
}

/**
* @param {string} event_name
* @param {Element} dom
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { flushSync } from 'svelte';
import { test } from '../../test';

export default test({
mode: ['client'],

test({ assert, target, logs }) {
const [b1] = target.querySelectorAll('button');

b1?.click();
b1?.click();
b1?.click();
flushSync();
assert.htmlEqual(target.innerHTML, '<section><button>clicks: 3</button></section>');
assert.deepEqual(logs, []);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script>
import {attach} from 'svelte/events';

let count = $state(0);

function increment(e) {
e.stopPropagation();
count += 1;
}

let sectionEl
$effect(() => {
return attach(sectionEl, 'click', () => {
console.log('logged from addEventListener');
});
});
</script>

<section bind:this={sectionEl} onclick={() => console.log('logged from onclick')}>
<button onclick={increment}>
clicks: {count}
</button>
</section>
11 changes: 11 additions & 0 deletions packages/svelte/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2336,6 +2336,17 @@ declare module 'svelte/transition' {
}) => () => TransitionConfig];
}

declare module 'svelte/events' {
/**
* Attaches a DOM event handler to an element and returns a function that detaches the event. The event handler
* will be processed through Svelte's internal event delegation system and is the preferred way to imperatively
* attach event handlers instead of using `addEventListener`.
*
*
*/
export function attach(dom: Element, event_name: string, handler: EventListener, options?: AddEventListenerOptions | undefined): () => void;
}

declare module 'svelte/types/compiler/preprocess' {
/** @deprecated import this from 'svelte/preprocess' instead */
export type MarkupPreprocessor = MarkupPreprocessor_1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,39 @@ Svelte provides reactive `Map`, `Set`, `Date` and `URL` classes. These can be im
<input bind:value={url.href} />
```

## `svelte/events`

Svelte provides a way of imperatively attaching DOM event listeners to elements using the `attach` export from `svelte/events`. This can be used in place of
Comment thread
trueadm marked this conversation as resolved.
Outdated
imperatively doing `element.addEventListener`, with that benefit that `attach` will allow Svelte to co-ordinate the event through its own event delegation system.

```js
// @filename: index.ts
const element: Element = null as any;
// ---cut---
import { attach } from 'svelte/events';

attach(element, 'click', () => {
console.log('element was clicked');
});
```

Additionally, `attach` returns a function that easily allows for removal of the attached event handler:

```js
// @filename: index.ts
const element: Element = null as any;
// ---cut---
import { attach } from 'svelte/events';

const remove = attach(element, 'click', () => {
console.log('element was clicked');
});
// ...
remove();
```

> Note: `attach` also accepts 4th optional argument for defining the options for the event handler. This matches that of the options argument (`EventListenerOptions`) for `addEventListener`.

## `svelte/server`

### `render`
Expand Down