From b51fcdd83a04cd6c6c6f87eae21d4e51c0db68d3 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Jun 2024 09:31:26 +0100 Subject: [PATCH 01/15] feat: add svelete/events package and export attach function --- .changeset/spicy-peas-vanish.md | 5 ++++ packages/svelte/package.json | 4 +++ packages/svelte/scripts/generate-types.js | 1 + packages/svelte/src/events/index.js | 1 + .../internal/client/dom/elements/events.js | 14 ++++++++++ .../samples/event-attach/_config.js | 17 ++++++++++++ .../samples/event-attach/main.svelte | 23 ++++++++++++++++ packages/svelte/types/index.d.ts | 4 +++ .../routes/docs/content/01-api/05-imports.md | 27 +++++++++++++++++++ 9 files changed, 96 insertions(+) create mode 100644 .changeset/spicy-peas-vanish.md create mode 100644 packages/svelte/src/events/index.js create mode 100644 packages/svelte/tests/runtime-runes/samples/event-attach/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/event-attach/main.svelte diff --git a/.changeset/spicy-peas-vanish.md b/.changeset/spicy-peas-vanish.md new file mode 100644 index 000000000000..7bcf42970226 --- /dev/null +++ b/.changeset/spicy-peas-vanish.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +feat: add svelete/events package and export attach function diff --git a/packages/svelte/package.json b/packages/svelte/package.json index bf6c66846658..5c891d52878d 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -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": { diff --git a/packages/svelte/scripts/generate-types.js b/packages/svelte/scripts/generate-types.js index c52693f5f342..bb195f5f8230 100644 --- a/packages/svelte/scripts/generate-types.js +++ b/packages/svelte/scripts/generate-types.js @@ -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` diff --git a/packages/svelte/src/events/index.js b/packages/svelte/src/events/index.js new file mode 100644 index 000000000000..f42e8bbf2513 --- /dev/null +++ b/packages/svelte/src/events/index.js @@ -0,0 +1 @@ +export { attach } from '../internal/client/dom/elements/events'; diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 480314f8ca8a..4f7b9d8b3c2c 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -66,6 +66,20 @@ export function create_event(event_name, dom, handler, options) { return target_handler; } +/** + * @param {Element} dom + * @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); + + return () => { + dom.removeEventListener(event_name, target_handler, options); + }; +} + /** * @param {string} event_name * @param {Element} dom diff --git a/packages/svelte/tests/runtime-runes/samples/event-attach/_config.js b/packages/svelte/tests/runtime-runes/samples/event-attach/_config.js new file mode 100644 index 000000000000..23e33a3f4acb --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-attach/_config.js @@ -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, '
'); + assert.deepEqual(logs, []); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/event-attach/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-attach/main.svelte new file mode 100644 index 000000000000..1edaf8498e47 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-attach/main.svelte @@ -0,0 +1,23 @@ + + +
console.log('logged from onclick')}> + +
diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 48827bfc7e00..600483f29db7 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2336,6 +2336,10 @@ declare module 'svelte/transition' { }) => () => TransitionConfig]; } +declare module 'svelte/events' { + 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; diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md index 61a4f46d410a..a35041d59872 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md @@ -115,6 +115,33 @@ Svelte provides reactive `Map`, `Set`, `Date` and `URL` classes. These can be im ``` +## `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 +imperatively doing `element.addEventListener`, with that benefit that `attach` will allow Svelte to co-ordinate the event through its own event delegation system. + +```js +import { attach } from 'svelte/events'; + +attach(element, 'click', () => { + console.log('click the element!'); +}); +``` + +Additionally, `attach` returns a function that easily allows for removal of the attached event handler: + +```js +import { attach } from 'svelte/events'; + +const remove = attach(element, 'click', () => { + console.log('click the element!'); +}); +// ... +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` From f170e00399221cc88fad673903f27a45acdc226b Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Jun 2024 09:32:41 +0100 Subject: [PATCH 02/15] feat: add svelete/events package and export attach function --- .changeset/spicy-peas-vanish.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/spicy-peas-vanish.md b/.changeset/spicy-peas-vanish.md index 7bcf42970226..b26c62badb1b 100644 --- a/.changeset/spicy-peas-vanish.md +++ b/.changeset/spicy-peas-vanish.md @@ -2,4 +2,4 @@ "svelte": patch --- -feat: add svelete/events package and export attach function +feat: add svelte/events package and export attach function From d45bc778f12a22eeeef79298d145498f7fd545a5 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Jun 2024 10:01:23 +0100 Subject: [PATCH 03/15] docs --- .../src/routes/docs/content/01-api/05-imports.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md index a35041d59872..94fffca2dd69 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md @@ -123,6 +123,8 @@ imperatively doing `element.addEventListener`, with that benefit that `attach` w ```js import { attach } from 'svelte/events'; +let element; + attach(element, 'click', () => { console.log('click the element!'); }); @@ -133,6 +135,8 @@ Additionally, `attach` returns a function that easily allows for removal of the ```js import { attach } from 'svelte/events'; +let element; + const remove = attach(element, 'click', () => { console.log('click the element!'); }); From 0a054761d605ff3e0d5d7fed087a037721b883d1 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Jun 2024 10:04:44 +0100 Subject: [PATCH 04/15] docs --- .../src/routes/docs/content/01-api/05-imports.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md index 94fffca2dd69..c7c4e1409e3a 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md @@ -123,7 +123,7 @@ imperatively doing `element.addEventListener`, with that benefit that `attach` w ```js import { attach } from 'svelte/events'; -let element; +const element = document.getElementById('el'); attach(element, 'click', () => { console.log('click the element!'); @@ -135,7 +135,7 @@ Additionally, `attach` returns a function that easily allows for removal of the ```js import { attach } from 'svelte/events'; -let element; +const element = document.getElementById('el'); const remove = attach(element, 'click', () => { console.log('click the element!'); From fab37398a408f786157b2a4944c684159d896ed9 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Jun 2024 10:15:32 +0100 Subject: [PATCH 05/15] docs --- .../src/routes/docs/content/01-api/05-imports.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md index c7c4e1409e3a..cd1c3c405f79 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md @@ -121,10 +121,11 @@ Svelte provides a way of imperatively attaching DOM event listeners to elements 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'; -const element = document.getElementById('el'); - attach(element, 'click', () => { console.log('click the element!'); }); @@ -133,10 +134,11 @@ attach(element, 'click', () => { 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 element = document.getElementById('el'); - const remove = attach(element, 'click', () => { console.log('click the element!'); }); From 0a4ad0c4282c15c1c6f64ab4cb95a53d1c73fe1d Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Jun 2024 10:25:30 +0100 Subject: [PATCH 06/15] feedback --- packages/svelte/src/internal/client/dom/elements/events.js | 4 ++++ .../src/routes/docs/content/01-api/05-imports.md | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 4f7b9d8b3c2c..1fad8c7ca53c 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -67,6 +67,10 @@ export function create_event(event_name, dom, handler, options) { } /** + * 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 * @param {string} event_name * @param {EventListener} handler diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md index cd1c3c405f79..b42d9a98acfc 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md @@ -127,7 +127,7 @@ const element: Element = null as any; import { attach } from 'svelte/events'; attach(element, 'click', () => { - console.log('click the element!'); + console.log('element was clicked'); }); ``` @@ -140,7 +140,7 @@ const element: Element = null as any; import { attach } from 'svelte/events'; const remove = attach(element, 'click', () => { - console.log('click the element!'); + console.log('element was clicked'); }); // ... remove(); From bf422e43313b4e8e447bcb559f0cf79b4b28ace4 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Jun 2024 10:25:51 +0100 Subject: [PATCH 07/15] feedback --- packages/svelte/types/index.d.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 600483f29db7..e0ffb5134a37 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2337,6 +2337,13 @@ declare module 'svelte/transition' { } 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; } From 1009d93aae95e624329ec8dcb77c223fb19dbff5 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Jun 2024 11:00:38 +0100 Subject: [PATCH 08/15] add more info --- .../src/routes/docs/content/01-api/05-imports.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md index b42d9a98acfc..c8e596d81284 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md @@ -120,6 +120,10 @@ Svelte provides reactive `Map`, `Set`, `Date` and `URL` classes. These can be im 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 imperatively doing `element.addEventListener`, with that benefit that `attach` will allow Svelte to co-ordinate the event through its own event delegation system. +> Svelte 5 has its own internal event delegation system that it uses for certain types of events. Rather than creating hundreds or thousands of invidivual +event handlers on each element, Svelte creates a single delegated event handler on the root DOM element and manages the bookkeeping itself. This can have a significant impact +on performance and memory usage. + ```js // @filename: index.ts const element: Element = null as any; From 1c3637d763a7a4bed74881a095d0881735c056b5 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Jun 2024 11:03:46 +0100 Subject: [PATCH 09/15] lint --- .../src/routes/docs/content/01-api/05-imports.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md index c8e596d81284..52a7bd01e9e3 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md +++ b/sites/svelte-5-preview/src/routes/docs/content/01-api/05-imports.md @@ -121,8 +121,8 @@ Svelte provides a way of imperatively attaching DOM event listeners to elements imperatively doing `element.addEventListener`, with that benefit that `attach` will allow Svelte to co-ordinate the event through its own event delegation system. > Svelte 5 has its own internal event delegation system that it uses for certain types of events. Rather than creating hundreds or thousands of invidivual -event handlers on each element, Svelte creates a single delegated event handler on the root DOM element and manages the bookkeeping itself. This can have a significant impact -on performance and memory usage. +> event handlers on each element, Svelte creates a single delegated event handler on the root DOM element and manages the bookkeeping itself. This can have a significant impact +> on performance and memory usage. ```js // @filename: index.ts From 022be9719f1559ff5f16b517f1e8ba7720204c0d Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Wed, 5 Jun 2024 13:01:42 +0100 Subject: [PATCH 10/15] rename --- .changeset/spicy-peas-vanish.md | 2 +- packages/svelte/src/events/index.js | 2 +- .../src/internal/client/dom/elements/events.js | 2 +- .../{event-attach => event-on}/_config.js | 0 .../{event-attach => event-on}/main.svelte | 4 ++-- packages/svelte/types/index.d.ts | 2 +- .../src/routes/docs/content/01-api/05-imports.md | 16 ++++++++-------- 7 files changed, 14 insertions(+), 14 deletions(-) rename packages/svelte/tests/runtime-runes/samples/{event-attach => event-on}/_config.js (100%) rename packages/svelte/tests/runtime-runes/samples/{event-attach => event-on}/main.svelte (81%) diff --git a/.changeset/spicy-peas-vanish.md b/.changeset/spicy-peas-vanish.md index b26c62badb1b..0c377b61899b 100644 --- a/.changeset/spicy-peas-vanish.md +++ b/.changeset/spicy-peas-vanish.md @@ -2,4 +2,4 @@ "svelte": patch --- -feat: add svelte/events package and export attach function +feat: add svelte/events package and export `on` function diff --git a/packages/svelte/src/events/index.js b/packages/svelte/src/events/index.js index f42e8bbf2513..00be562bfc19 100644 --- a/packages/svelte/src/events/index.js +++ b/packages/svelte/src/events/index.js @@ -1 +1 @@ -export { attach } from '../internal/client/dom/elements/events'; +export { on } from '../internal/client/dom/elements/events'; diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 1fad8c7ca53c..b30d1af6a753 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -76,7 +76,7 @@ export function create_event(event_name, dom, handler, options) { * @param {EventListener} handler * @param {AddEventListenerOptions} [options] */ -export function attach(dom, event_name, handler, options = {}) { +export function on(dom, event_name, handler, options = {}) { var target_handler = create_event(event_name, dom, handler, options); return () => { diff --git a/packages/svelte/tests/runtime-runes/samples/event-attach/_config.js b/packages/svelte/tests/runtime-runes/samples/event-on/_config.js similarity index 100% rename from packages/svelte/tests/runtime-runes/samples/event-attach/_config.js rename to packages/svelte/tests/runtime-runes/samples/event-on/_config.js diff --git a/packages/svelte/tests/runtime-runes/samples/event-attach/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-on/main.svelte similarity index 81% rename from packages/svelte/tests/runtime-runes/samples/event-attach/main.svelte rename to packages/svelte/tests/runtime-runes/samples/event-on/main.svelte index 1edaf8498e47..2ab365590889 100644 --- a/packages/svelte/tests/runtime-runes/samples/event-attach/main.svelte +++ b/packages/svelte/tests/runtime-runes/samples/event-on/main.svelte @@ -1,5 +1,5 @@