;
* }} State
*/
@@ -481,7 +494,7 @@ const instance_script = {
}
}
- state.needs_run = true;
+ state.legacy_imports.add('run');
const is_block_stmt = node.body.type === 'BlockStatement';
const start_end = /** @type {number} */ (node.body.start);
// TODO try to find out if we can use $derived.by instead?
@@ -489,7 +502,7 @@ const instance_script = {
state.str.update(
/** @type {number} */ (node.start),
start_end + 1,
- `${state.run_name}(() => {`
+ `${state.legacy_imports_names.get('run')}(() => {`
);
const end = /** @type {number} */ (node.body.end);
state.str.update(end - 1, end, '});');
@@ -497,7 +510,7 @@ const instance_script = {
state.str.update(
/** @type {number} */ (node.start),
start_end,
- `${state.run_name}(() => {\n${state.indent}`
+ `${state.legacy_imports_names.get('run')}(() => {\n${state.indent}`
);
state.str.indent(state.indent, {
exclude: [
@@ -701,25 +714,48 @@ function handle_events(element, state) {
// Check if prop already set, could happen when on:click on different elements
let local = state.props.find((prop) => prop.exported === exported)?.local;
- const last = nodes[nodes.length - 1];
- const payload_name =
- last.expression?.type === 'ArrowFunctionExpression' &&
- last.expression.params[0]?.type === 'Identifier'
- ? last.expression.params[0].name
- : generate_event_name(last, state);
+ if (nodes.length > 1) {
+ state.legacy_imports.add('handlers');
+ }
+
+ const handlers = [];
for (let i = 0; i < nodes.length; i += 1) {
- let prepend = '';
const node = nodes[i];
+ const payload_name =
+ (node.expression?.type === 'ArrowFunctionExpression' ||
+ node.expression?.type === 'FunctionExpression') &&
+ node.expression.params[0]?.type === 'Identifier'
+ ? node.expression.params[0].name
+ : generate_event_name(node, state);
const indent = get_indent(state, node, element);
- const args = `,"${node.name}"${node.modifiers.length > 0 ? `,${JSON.stringify(node.modifiers)}` : ''}`;
+ const new_line_index = state.str.original.lastIndexOf('\n', node.start);
+ const needs_line_delete =
+ state.str.original.substring(new_line_index, node.start).trim() === '' && i !== 0;
+
+ // always move once as the first modifier
+ const sorted_modifier = [...node.modifiers].sort((a, b) =>
+ a === 'once' ? 1 : b === 'once' ? -1 : 0
+ );
+
if (node.expression) {
let body = state.str.original.substring(
/** @type {number} */ (node.expression.start),
/** @type {number} */ (node.expression.end)
);
- prepend += `use:${state.listener_name}={[${body}${args}]}`;
- state.needs_listener = true;
+
+ for (const modifier of sorted_modifier) {
+ if (modifier !== 'capture' && modifier !== 'passive' && modifier !== 'nonpassive') {
+ state.legacy_imports.add(modifier);
+ body = `${state.legacy_imports_names.get(modifier)}(${body})`;
+ }
+ }
+ handlers.push({
+ handler: body,
+ indent,
+ needs_line_delete
+ });
+ state.str.remove(needs_line_delete ? new_line_index : node.start, node.end);
} else {
if (!local) {
local = state.scope.generate(`on${node.name}`);
@@ -732,11 +768,40 @@ function handle_events(element, state) {
type: '(event: any) => void'
});
}
- prepend += `use:${state.listener_name}={[(${payload_name})=>{${local}?.(${payload_name});}${args}]}`;
- state.needs_listener = true;
+ let body = `(${payload_name})=>{`;
+
+ let added_modifiers = 0;
+
+ for (const modifier of sorted_modifier) {
+ if (modifier !== 'capture' && modifier !== 'passive' && modifier !== 'nonpassive') {
+ added_modifiers++;
+ body = `\n${indent}${state.legacy_imports_names.get(modifier)}(${body}`;
+ state.legacy_imports.add(modifier);
+ }
+ }
+
+ body += `${local}?.(${payload_name});}${')'.repeat(added_modifiers)}`;
+ handlers.push({
+ handler: body,
+ indent
+ });
+ state.str.remove(needs_line_delete ? new_line_index : node.start, node.end);
}
+ }
+
+ const first_node = nodes[0];
- state.str.overwrite(node.start, node.end, prepend);
+ if (first_node) {
+ let handlers_body = '';
+ for (let handler of handlers) {
+ handlers_body += `${handler.needs_line_delete || nodes.length > 1 ? `\n${handler.indent}` : ''}${handler.handler},`;
+ }
+ handlers_body = handlers_body.substring(0, handlers_body.length - 1);
+ state.str.overwrite(
+ first_node.start,
+ first_node.end,
+ `${name}={${nodes.length > 1 ? `${state.legacy_imports_names.get('handlers')}(` : ''}${handlers_body}${nodes.length > 1 ? ')' : ''}}`
+ );
}
}
}
diff --git a/packages/svelte/src/legacy/legacy-client.js b/packages/svelte/src/legacy/legacy-client.js
index 57650a29a5..d3d59ad1ed 100644
--- a/packages/svelte/src/legacy/legacy-client.js
+++ b/packages/svelte/src/legacy/legacy-client.js
@@ -172,32 +172,110 @@ export function run(fn) {
}
/**
- * @param {EventTarget} node
- * @param {[EventListener, string, string[]]} options
+ * Function to mimic the multiple listeners available in svelte 4
+ * @param {EventListener[]} handlers
+ * @returns {EventListener}
*/
-export function listener(node, [handler, type, modifiers = []]) {
- const aborter = new AbortController();
- node.addEventListener(
- type,
- (e) => {
- for (let modifier of modifiers) {
- // @ts-expect-error modifier is just a string
- if (modifier in e && typeof e[modifier] === 'function') {
- // @ts-expect-error modifier is just a string
- e[modifier]();
- }
+export function handlers(...handlers) {
+ return function (event) {
+ const { stopImmediatePropagation } = event;
+ let stopped = false;
+
+ event.stopImmediatePropagation = () => {
+ stopped = true;
+ stopImmediatePropagation.call(event);
+ };
+
+ const errors = [];
+
+ for (const handler of handlers) {
+ try {
+ // @ts-expect-error `this` is not typed
+ handler.call(this, event);
+ } catch (e) {
+ errors.push(e);
}
- handler?.(e);
- },
- {
- signal: aborter.signal,
- passive: modifiers.includes('passive'),
- capture: modifiers.includes('capture')
+
+ if (stopped) {
+ break;
+ }
+ }
+
+ for (let error of errors) {
+ queueMicrotask(() => {
+ throw error;
+ });
+ }
+ };
+}
+
+/**
+ * Migration helper to substitute the `stopImmediatePropagation` event modifier
+ * @param {EventListener} fn
+ */
+export function stopImmediatePropagation(fn) {
+ return (/**@type {Event}*/ e) => {
+ e.stopImmediatePropagation();
+ fn(e);
+ };
+}
+
+/**
+ * Migration helper to substitute the `preventDefault` event modifier
+ * @param {EventListener} fn
+ */
+export function preventDefault(fn) {
+ return (/**@type {Event}*/ e) => {
+ e.preventDefault();
+ fn(e);
+ };
+}
+
+/**
+ * Migration helper to substitute the `stopPropagation` event modifier
+ * @param {EventListener} fn
+ */
+export function stopPropagation(fn) {
+ return (/**@type {Event}*/ e) => {
+ e.stopPropagation();
+ fn(e);
+ };
+}
+
+/**
+ * Migration helper to substitute the `trusted` event modifier
+ * @param {EventListener} fn
+ */
+export function trusted(fn) {
+ return (/**@type {Event}*/ e) => {
+ if (e.isTrusted) {
+ fn(e);
}
- );
- return {
- destroy() {
- aborter.abort();
+ };
+}
+
+/**
+ * Migration helper to substitute the `self` event modifier
+ * @param {EventListener} fn
+ */
+export function self(fn) {
+ return (/**@type {Event}*/ e) => {
+ if (e.target === e.currentTarget) {
+ fn(e);
+ }
+ };
+}
+
+/**
+ * Migration helper to substitute the `once` event modifier
+ * @param {EventListener} fn
+ */
+export function once(fn) {
+ let executed = false;
+ return (/**@type {Event}*/ e) => {
+ if (!executed) {
+ executed = true;
+ fn(e);
}
};
}
diff --git a/packages/svelte/tests/migrate/samples/event-handlers-with-alias/input.svelte b/packages/svelte/tests/migrate/samples/event-handlers-with-alias/input.svelte
new file mode 100644
index 0000000000..b30f5cd14a
--- /dev/null
+++ b/packages/svelte/tests/migrate/samples/event-handlers-with-alias/input.svelte
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/svelte/tests/migrate/samples/event-handlers-with-alias/output.svelte b/packages/svelte/tests/migrate/samples/event-handlers-with-alias/output.svelte
new file mode 100644
index 0000000000..cd5d626339
--- /dev/null
+++ b/packages/svelte/tests/migrate/samples/event-handlers-with-alias/output.svelte
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/svelte/tests/migrate/samples/event-handlers/input.svelte b/packages/svelte/tests/migrate/samples/event-handlers/input.svelte
index 2557062727..204d9f9a1d 100644
--- a/packages/svelte/tests/migrate/samples/event-handlers/input.svelte
+++ b/packages/svelte/tests/migrate/samples/event-handlers/input.svelte
@@ -1,4 +1,5 @@
+
@@ -10,12 +11,33 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/svelte/tests/migrate/samples/event-handlers/output.svelte b/packages/svelte/tests/migrate/samples/event-handlers/output.svelte
index a31e3e9d54..61ccf736da 100644
--- a/packages/svelte/tests/migrate/samples/event-handlers/output.svelte
+++ b/packages/svelte/tests/migrate/samples/event-handlers/output.svelte
@@ -1,40 +1,81 @@
-
-