From 48e620515dbb79ca8caf39634f2fb601d6a8534b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:04:15 -0400 Subject: [PATCH 1/4] Version Packages (next) (#11926) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 5 +++++ packages/svelte/CHANGELOG.md | 14 ++++++++++++++ packages/svelte/package.json | 2 +- packages/svelte/src/version.js | 2 +- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 1a6fc5d623..66eb73639f 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -57,6 +57,7 @@ "chilled-pumas-invite", "chilled-seas-jog", "chilly-dolphins-lick", + "chilly-laws-juggle", "chilly-pans-raise", "chilly-rocks-hug", "chilly-snakes-scream", @@ -69,6 +70,7 @@ "cold-beans-tease", "cold-birds-own", "cold-cheetahs-judge", + "cold-lamps-accept", "cold-masks-learn", "cool-actors-tan", "cool-ants-leave", @@ -219,6 +221,7 @@ "honest-pans-kick", "hot-cooks-repair", "hot-jobs-tap", + "hot-sloths-clap", "hungry-boxes-relate", "hungry-dots-fry", "hungry-pants-push", @@ -337,6 +340,7 @@ "olive-seals-sell", "olive-shirts-complain", "olive-socks-kick", + "orange-comics-prove", "orange-crews-rescue", "orange-dingos-poke", "orange-masks-exercise", @@ -511,6 +515,7 @@ "tall-tigers-wait", "tame-cycles-kneel", "tame-dots-battle", + "tame-goats-bow", "tame-spies-drum", "tasty-cheetahs-appear", "tasty-numbers-perform", diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index b154e1a804..d0b85f3143 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,19 @@ # svelte +## 5.0.0-next.151 + +### Patch Changes + +- fix: relax `Component` type ([#11929](https://github.com/sveltejs/svelte/pull/11929)) + +- fix: sort `{@const ...}` tags topologically in legacy mode ([#11908](https://github.com/sveltejs/svelte/pull/11908)) + +- chore: deprecate html in favour of body for render() ([#11927](https://github.com/sveltejs/svelte/pull/11927)) + +- fix: append start/end info to `AssignmentPattern` and `VariableDeclarator` ([#11930](https://github.com/sveltejs/svelte/pull/11930)) + +- fix: relax slot prop validation on components ([#11923](https://github.com/sveltejs/svelte/pull/11923)) + ## 5.0.0-next.150 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index 4428fd7795..e13b67f7d6 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.0.0-next.150", + "version": "5.0.0-next.151", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/version.js b/packages/svelte/src/version.js index d6e46b331b..7beb118796 100644 --- a/packages/svelte/src/version.js +++ b/packages/svelte/src/version.js @@ -6,5 +6,5 @@ * https://svelte.dev/docs/svelte-compiler#svelte-version * @type {string} */ -export const VERSION = '5.0.0-next.150'; +export const VERSION = '5.0.0-next.151'; export const PUBLIC_VERSION = '5'; From ba017db89d744b827267bd8f1470db6336d21780 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 6 Jun 2024 19:33:02 +0200 Subject: [PATCH 2/4] docs: make legacy.componentApi more visible (#11924) * docs: make legacy.componentApi more visible People didn't know that this exists, so we should make it more visible through having it be part of the error message, and calling it out in the docs with more details * clarify * flesh out message, put error behind a function * make it work with HMR * fix * fix * this makes it work. i don't fully understand why * changeset * changeset * Delete .changeset/twelve-foxes-press.md --------- Co-authored-by: Rich Harris --- .changeset/flat-ghosts-fly.md | 5 +++++ .../svelte/messages/client-errors/errors.md | 4 ++++ .../3-transform/client/transform-client.js | 12 ++---------- packages/svelte/src/internal/client/dev/hmr.js | 8 ++++++-- .../svelte/src/internal/client/dev/legacy.js | 7 +++++++ packages/svelte/src/internal/client/errors.js | 18 ++++++++++++++++++ packages/svelte/src/internal/client/index.js | 2 +- .../content/03-appendix/02-breaking-changes.md | 11 ++++++++++- 8 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 .changeset/flat-ghosts-fly.md diff --git a/.changeset/flat-ghosts-fly.md b/.changeset/flat-ghosts-fly.md new file mode 100644 index 0000000000..bcd10f7066 --- /dev/null +++ b/.changeset/flat-ghosts-fly.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: make `legacy.componentApi` option more visible diff --git a/packages/svelte/messages/client-errors/errors.md b/packages/svelte/messages/client-errors/errors.md index bb8773a3bd..ec543765cf 100644 --- a/packages/svelte/messages/client-errors/errors.md +++ b/packages/svelte/messages/client-errors/errors.md @@ -14,6 +14,10 @@ > %parent% called `%method%` on an instance of %component%, which is no longer valid in Svelte 5. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information +## component_api_invalid_new + +> Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `legacy.componentApi` compiler option to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information + ## each_key_duplicate > Keyed each block has duplicate key at indexes %a% and %b% diff --git a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js index 0cdfd1e5df..da2502b6f5 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/transform-client.js @@ -452,7 +452,7 @@ export function client_component(source, analysis, options) { body.unshift(b.imports([['createClassComponent', '$$_createClassComponent']], 'svelte/legacy')); component_block.body.unshift( b.if( - b.binary('===', b.id('new.target'), b.id(analysis.name)), + b.id('new.target'), b.return( b.call( '$$_createClassComponent', @@ -463,15 +463,7 @@ export function client_component(source, analysis, options) { ) ); } else if (options.dev) { - component_block.body.unshift( - b.if( - b.binary('===', b.id('new.target'), b.id(analysis.name)), - b.throw_error( - `Instantiating a component with \`new\` is no longer valid in Svelte 5. ` + - 'See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information' - ) - ) - ); + component_block.body.unshift(b.stmt(b.call('$.check_target', b.id('new.target')))); } if (state.events.size > 0) { diff --git a/packages/svelte/src/internal/client/dev/hmr.js b/packages/svelte/src/internal/client/dev/hmr.js index 0f79607707..9ba2a69897 100644 --- a/packages/svelte/src/internal/client/dev/hmr.js +++ b/packages/svelte/src/internal/client/dev/hmr.js @@ -1,6 +1,7 @@ import { block, branch, destroy_effect } from '../reactivity/effects.js'; import { set_should_intro } from '../render.js'; import { get } from '../runtime.js'; +import { check_target } from './legacy.js'; /** * @template {(anchor: Comment, props: any) => any} Component @@ -11,7 +12,7 @@ export function hmr(source) { * @param {Comment} anchor * @param {any} props */ - return (anchor, props) => { + return function (anchor, props) { let instance = {}; /** @type {import("#client").Effect} */ @@ -31,7 +32,10 @@ export function hmr(source) { // preserve getters/setters Object.defineProperties( instance, - Object.getOwnPropertyDescriptors(component(anchor, props)) + Object.getOwnPropertyDescriptors( + // @ts-expect-error + new.target ? new component(anchor, props) : component(anchor, props) + ) ); set_should_intro(true); }); diff --git a/packages/svelte/src/internal/client/dev/legacy.js b/packages/svelte/src/internal/client/dev/legacy.js index 758e1a95b5..1262c62db0 100644 --- a/packages/svelte/src/internal/client/dev/legacy.js +++ b/packages/svelte/src/internal/client/dev/legacy.js @@ -2,6 +2,13 @@ import * as e from '../errors.js'; import { current_component_context } from '../runtime.js'; import { get_component } from './ownership.js'; +/** @param {Function & { filename: string }} target */ +export function check_target(target) { + if (target) { + e.component_api_invalid_new(target.filename ?? 'a component', target.name); + } +} + export function legacy_api() { const component = current_component_context?.function; diff --git a/packages/svelte/src/internal/client/errors.js b/packages/svelte/src/internal/client/errors.js index 2ead3963c5..981f40a218 100644 --- a/packages/svelte/src/internal/client/errors.js +++ b/packages/svelte/src/internal/client/errors.js @@ -75,6 +75,24 @@ export function component_api_changed(parent, method, component) { } } +/** + * Attempted to instantiate %component% with `new %name%`, which is no longer valid in Svelte 5. If this component is not under your control, set the `legacy.componentApi` compiler option to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information + * @param {string} component + * @param {string} name + * @returns {never} + */ +export function component_api_invalid_new(component, name) { + if (DEV) { + const error = new Error(`${"component_api_invalid_new"}\n${`Attempted to instantiate ${component} with \`new ${name}\`, which is no longer valid in Svelte 5. If this component is not under your control, set the \`legacy.componentApi\` compiler option to keep it working. See https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more information`}`); + + error.name = 'Svelte error'; + throw error; + } else { + // TODO print a link to the documentation + throw new Error("component_api_invalid_new"); + } +} + /** * Keyed each block has duplicate key `%value%` at indexes %a% and %b% * @param {string} a diff --git a/packages/svelte/src/internal/client/index.js b/packages/svelte/src/internal/client/index.js index 65983da3e2..1194570c87 100644 --- a/packages/svelte/src/internal/client/index.js +++ b/packages/svelte/src/internal/client/index.js @@ -7,7 +7,7 @@ export { mark_module_end, add_owner_effect } from './dev/ownership.js'; -export { legacy_api } from './dev/legacy.js'; +export { check_target, legacy_api } from './dev/legacy.js'; export { inspect } from './dev/inspect.js'; export { await_block as await } from './dom/blocks/await.js'; export { if_block as if } from './dom/blocks/if.js'; diff --git a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md index 81b28ce817..5b08103d80 100644 --- a/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md +++ b/sites/svelte-5-preview/src/routes/docs/content/03-appendix/02-breaking-changes.md @@ -70,7 +70,16 @@ import App from './App.svelte' export default app; ``` -If this component is not under your control, you can use the `legacy.componentApi` compiler option for auto-applied backwards compatibility (note that this adds a bit of overhead to each component). This will also add `$set` and `$on` methods for all component instances you get through `bind:this`. +If this component is not under your control, you can use the `legacy.componentApi` compiler option for auto-applied backwards compatibility, which means code using `new Component(...)` keeps working without adjustments (note that this adds a bit of overhead to each component). This will also add `$set` and `$on` methods for all component instances you get through `bind:this`. + +```js +/// svelte.config.js +export default { + compilerOptions: { + legacy: { componentApi: true } + } +}; +``` ### Server API changes From fdecad6cddef0d133d893ca9e0d830ab2a939896 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 6 Jun 2024 14:19:24 -0400 Subject: [PATCH 3/4] chore: better bind_property implementation (#11904) - better name for bidirectional bindings - cleanup implementation --- .../3-transform/client/visitors/template.js | 5 ++- .../svelte/src/compiler/phases/bindings.js | 6 ++-- .../client/dom/elements/bindings/universal.js | 36 +++++++++---------- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js index e7bd5765f6..195f064864 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/template.js @@ -2747,10 +2747,9 @@ export const template_visitors = { '$.bind_property', b.literal(node.name), b.literal(property.event), - b.literal(property.type ?? 'get'), state.node, - getter, - setter + setter, + property.bidirectional && getter ); } else { // special cases diff --git a/packages/svelte/src/compiler/phases/bindings.js b/packages/svelte/src/compiler/phases/bindings.js index ad998b3abf..eee84473a0 100644 --- a/packages/svelte/src/compiler/phases/bindings.js +++ b/packages/svelte/src/compiler/phases/bindings.js @@ -2,7 +2,7 @@ * @typedef BindingProperty * @property {string} [event] This is set if the binding corresponds to the property name on the dom element it's bound to * and there's an event that notifies of a change to that property - * @property {string} [type] Set this to `set` if updates are written to the dom property + * @property {boolean} [bidirectional] Set this to `true` if updates are written to the dom property * @property {boolean} [omit_in_ssr] Set this to true if the binding should not be included in SSR * @property {string[]} [valid_elements] If this is set, the binding is only valid on the given elements * @property {string[]} [invalid_elements] If this is set, the binding is invalid on the given elements @@ -175,7 +175,7 @@ export const binding_properties = { // checkbox/radio indeterminate: { event: 'change', - type: 'set', + bidirectional: true, valid_elements: ['input'], omit_in_ssr: true // no corresponding attribute }, @@ -200,7 +200,7 @@ export const binding_properties = { }, open: { event: 'toggle', - type: 'set', + bidirectional: true, valid_elements: ['details'] }, value: { diff --git a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js index f42f67d594..7e8b3f3bf4 100644 --- a/packages/svelte/src/internal/client/dom/elements/bindings/universal.js +++ b/packages/svelte/src/internal/client/dom/elements/bindings/universal.js @@ -33,40 +33,36 @@ export function bind_content_editable(property, element, get_value, update) { /** * @param {string} property * @param {string} event_name - * @param {'get' | 'set'} type * @param {Element} element - * @param {() => unknown} get_value - * @param {(value: unknown) => void} update + * @param {(value: unknown) => void} set + * @param {() => unknown} [get] * @returns {void} */ -export function bind_property(property, event_name, type, element, get_value, update) { - var target_handler = () => { +export function bind_property(property, event_name, element, set, get) { + var handler = () => { // @ts-ignore - update(element[property]); + set(element[property]); }; - element.addEventListener(event_name, target_handler); + element.addEventListener(event_name, handler); - if (type === 'set') { + if (get) { render_effect(() => { // @ts-ignore - element[property] = get_value(); + element[property] = get(); }); + } else { + handler(); } - if (type === 'get') { - // @ts-ignore - update(element[property]); - } - - render_effect(() => { - // @ts-ignore - if (element === document.body || element === window || element === document) { + // @ts-ignore + if (element === document.body || element === window || element === document) { + render_effect(() => { return () => { - element.removeEventListener(event_name, target_handler); + element.removeEventListener(event_name, handler); }; - } - }); + }); + } } /** From aadf629580a92953c99cb368ce0d9690bacd8ed3 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Thu, 6 Jun 2024 20:25:05 +0100 Subject: [PATCH 4/4] feat: add svelte/events package and export on function (#11912) * feat: add svelete/events package and export attach function * feat: add svelete/events package and export attach function * docs * docs * docs * feedback * feedback * add more info * lint * rename * add package to autocomplete * rename args * fix * update inline docs * tweak docs --------- Co-authored-by: Rich Harris --- .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 | 18 ++++++++++++++ .../runtime-runes/samples/event-on/_config.js | 17 +++++++++++++ .../samples/event-on/main.svelte | 23 ++++++++++++++++++ packages/svelte/types/index.d.ts | 11 +++++++++ .../svelte-5-preview/src/lib/autocomplete.js | 1 + .../routes/docs/content/01-api/05-imports.md | 24 +++++++++++++++++++ 10 files changed, 105 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-on/_config.js create mode 100644 packages/svelte/tests/runtime-runes/samples/event-on/main.svelte diff --git a/.changeset/spicy-peas-vanish.md b/.changeset/spicy-peas-vanish.md new file mode 100644 index 0000000000..0c377b6189 --- /dev/null +++ b/.changeset/spicy-peas-vanish.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +feat: add svelte/events package and export `on` function diff --git a/packages/svelte/package.json b/packages/svelte/package.json index e13b67f7d6..62a8af6dbf 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 c52693f5f3..bb195f5f82 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 0000000000..ba5bd46c52 --- /dev/null +++ b/packages/svelte/src/events/index.js @@ -0,0 +1 @@ +export { on } from '../internal/client/dom/elements/events.js'; diff --git a/packages/svelte/src/internal/client/dom/elements/events.js b/packages/svelte/src/internal/client/dom/elements/events.js index 480314f8ca..33cb2c3ca0 100644 --- a/packages/svelte/src/internal/client/dom/elements/events.js +++ b/packages/svelte/src/internal/client/dom/elements/events.js @@ -66,6 +66,24 @@ export function create_event(event_name, dom, handler, options) { return target_handler; } +/** + * Attaches an event handler to an element and returns a function that removes the handler. Using this + * rather than `addEventListener` will preserve the correct order relative to handlers added declaratively + * (with attributes like `onclick`), which use event delegation for performance reasons + * + * @param {Element} element + * @param {string} type + * @param {EventListener} handler + * @param {AddEventListenerOptions} [options] + */ +export function on(element, type, handler, options = {}) { + var target_handler = create_event(type, element, handler, options); + + return () => { + element.removeEventListener(type, target_handler, options); + }; +} + /** * @param {string} event_name * @param {Element} dom diff --git a/packages/svelte/tests/runtime-runes/samples/event-on/_config.js b/packages/svelte/tests/runtime-runes/samples/event-on/_config.js new file mode 100644 index 0000000000..23e33a3f4a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-on/_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-on/main.svelte b/packages/svelte/tests/runtime-runes/samples/event-on/main.svelte new file mode 100644 index 0000000000..2ab3655908 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/event-on/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 acc19259c3..3d60d81d12 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2340,6 +2340,17 @@ declare module 'svelte/transition' { }) => () => TransitionConfig]; } +declare module 'svelte/events' { + /** + * Attaches an event handler to an element and returns a function that removes the handler. Using this + * rather than `addEventListener` will preserve the correct order relative to handlers added declaratively + * (with attributes like `onclick`), which use event delegation for performance reasons + * + * + */ + export function on(element: Element, type: 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/lib/autocomplete.js b/sites/svelte-5-preview/src/lib/autocomplete.js index ff0c2f7410..7fe6b8e4c1 100644 --- a/sites/svelte-5-preview/src/lib/autocomplete.js +++ b/sites/svelte-5-preview/src/lib/autocomplete.js @@ -145,6 +145,7 @@ export function autocomplete(context, selected, files) { 'svelte', 'svelte/animate', 'svelte/easing', + 'svelte/events', 'svelte/legacy', 'svelte/motion', 'svelte/reactivity', 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 34165df769..2cbc26f936 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,30 @@ Svelte provides reactive `Map`, `Set`, `Date` and `URL` classes. These can be im ``` +## `svelte/events` + +Where possible, event handlers added with [attributes like `onclick`](/docs/event-handlers) use a technique called _event delegation_. It works by creating a single handler for each event type on the root DOM element, rather than creating a handler for each element, resulting in better performance and memory usage. + +Delegated event handlers run after other event handlers. In other words, a handler added programmatically with [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) will run _before_ a handler added declaratively with `onclick`, regardless of their relative position in the DOM ([demo](/#H4sIAAAAAAAAE41Sy2rDMBD8lUUXJxDiu-sYeugt_YK6h8RaN6LyykgrQzH6965shxJooQc_RhrNzA6aVW8sBlW9zYouA6pKPY-jOij-GjMIE1pGwcFF3-WVOnTejNy01LIZRucZZnD06iIxJOi9G6BYjxVPmZQfiwzaTBkL2ti73R5ODcwLiftIHRtHcLuQtuhlc9tpuSyBbyZAuLloNfhIELBzpO8E-Q_O4tG6j13hIqO_y0BvPOpiv0bhtJ1Y3pLoeNH6ZULiswmMJLZFZ033WRzuAvstdMseOXqCh9SriMfBTfgPnZxg-aYM6_KnS6pFCK6GdJVHPc0C01JyfY0slUnHi-JpfgjwSzUycdgmfOjFEP3RS1qdhJ8dYMDFt1yNmxxU0jRyCwanTW9Qq4p9xPSevgHI3m43QAIAAA==)). It also means that calling `event.stopPropagation()` inside a declarative handler _won't_ prevent the programmatic handler (created inside an action, for example) from running. + +To preserve the relative order, use `on` rather than `addEventListener` ([demo](/#H4sIAAAAAAAAE3VRy26DMBD8lZUvECkqdwpI_YB-QdJDgpfGqlkjex2pQv73rnmoStQeMB52dnZmmdVgLAZVn2ZFlxFVrd6mSR0Vf08ZhDtaRsHBRd_nL03ovZm4O9OZzTg5zzCDo3cXiSHB4N0IxdpWvD6RnuoV3pE4rLT8WGTQ5p6xoE20LA_QdjAvJB4i9WxE6nYhbdFLcaucuaqAbyZAuLloNfhIELB3pHeC3IOz-GLdZ1m4yOh3GRiMR10cViucto7l9MjRk9gvxdsRit6a_qs47q1rT8qvpvpdDjXChqshXWdT7SwwLVtrrpElnAguSu38EPCPEOItbF4eEhiifxKkdZLw8wQYcZlbrYO7bFTcdPJbR6fNYFCrmn3E9JF-AJZOg9MRAgAA)): + +```js +// @filename: index.ts +const element: Element = null as any; +// ---cut--- +import { on } from 'svelte/events'; + +const off = on(element, 'click', () => { + console.log('element was clicked'); +}); + +// later, if we need to remove the event listener: +off(); +``` + +`on` also accepts an optional fourth argument which matches the options argument for `addEventListener`. + ## `svelte/server` ### `render`