Skip to content

Commit

Permalink
feat: delegate action events
Browse files Browse the repository at this point in the history
Patches dom instances when they are passed to actions so that addEventListener delegates events. This ensures that people adding listeners imperatively will still have the event order preserved.
  • Loading branch information
dummdidumm committed May 2, 2024
1 parent 1f9ad03 commit b8c32f5
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 3 deletions.
61 changes: 61 additions & 0 deletions packages/svelte/src/internal/client/dom/elements/actions.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DelegatedEvents } from '../../../../constants.js';
import { effect, render_effect } from '../../reactivity/effects.js';
import { deep_read_state, untrack } from '../../runtime.js';

Expand All @@ -10,6 +11,66 @@ import { deep_read_state, untrack } from '../../runtime.js';
*/
export function action(dom, action, get_value) {
effect(() => {
var addEventListener = dom.addEventListener;

Check failure on line 14 in packages/svelte/src/internal/client/dom/elements/actions.js

View workflow job for this annotation

GitHub Actions / Lint

Identifier 'addEventListener' does not match for svelte's naming conventions
var removeEventListener = dom.removeEventListener;

Check failure on line 15 in packages/svelte/src/internal/client/dom/elements/actions.js

View workflow job for this annotation

GitHub Actions / Lint

Identifier 'removeEventListener' does not match for svelte's naming conventions

/**
* @param {string} event_name
* @param {EventListenerOrEventListenerObject} listener
* @param {AddEventListenerOptions} options
*/
dom.addEventListener = function (event_name, listener, options) {
var delegated =
DelegatedEvents.includes(event_name) && !options?.capture && typeof listener === 'function';

if (delegated) {
var event_key = `__${event_name}`;
// @ts-ignore
var existing = dom[event_key];
if (existing != null) {
if (!Array.isArray(existing)) {
// @ts-ignore
dom[event_key] = [existing];
}
existing.push(listener);
} else {
// @ts-ignore
dom[event_key] = listener;
}
} else {
addEventListener.call(dom, event_name, listener, options);

Check failure on line 41 in packages/svelte/src/internal/client/dom/elements/actions.js

View workflow job for this annotation

GitHub Actions / Lint

Identifier 'addEventListener' does not match for svelte's naming conventions
}
};

/**
* @param {string} event_name
* @param {EventListenerOrEventListenerObject} listener
* @param {AddEventListenerOptions} options
*/
dom.removeEventListener = function (event_name, listener, options) {
var delegated =
DelegatedEvents.includes(event_name) && !options?.capture && typeof listener === 'function';

if (delegated) {
var event_key = `__${event_name}`;
// @ts-ignore
var existing = dom[event_key];
if (existing != null) {
if (Array.isArray(existing)) {
var index = existing.indexOf(listener);
if (index !== -1) {
existing.splice(index, 1);
}
} else {
// @ts-ignore
dom[event_key] = null;
}
}
} else {
removeEventListener.call(dom, event_name, listener, options);

Check failure on line 70 in packages/svelte/src/internal/client/dom/elements/actions.js

View workflow job for this annotation

GitHub Actions / Lint

Identifier 'removeEventListener' does not match for svelte's naming conventions
}
};

var payload = untrack(() => action(dom, get_value?.()) || {});

if (get_value && payload?.update) {
Expand Down
30 changes: 27 additions & 3 deletions packages/svelte/src/internal/client/dom/elements/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
const opts = {};
let event_name = key.slice(2);
var delegated = DelegatedEvents.includes(event_name);
var event_key = `__${event_name}`;
// @ts-ignore
var existing = delegated && element[event_key];

if (
event_name.endsWith('capture') &&
Expand All @@ -134,8 +137,18 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
opts.capture = true;
}

if (!delegated && prev?.[key]) {
element.removeEventListener(event_name, /** @type {any} */ (prev[key]), opts);
if (prev?.[key]) {
if (!delegated) {
element.removeEventListener(event_name, /** @type {any} */ (prev[key]), opts);
} else {
if (Array.isArray(existing)) {
// @ts-ignore
element[event_key] = existing.filter((fn) => fn !== prev[key]);
} else {
// @ts-ignore
element[event_key] = null;
}
}
}

if (value != null) {
Expand All @@ -146,8 +159,19 @@ export function set_attributes(element, prev, next, lowercase_attributes, css_ha
element.addEventListener(event_name, value, opts);
}
} else {
var event_key = `__${event_name}`;

Check failure on line 162 in packages/svelte/src/internal/client/dom/elements/attributes.js

View workflow job for this annotation

GitHub Actions / Lint

'event_key' is already defined
// @ts-ignore
element[`__${event_name}`] = value;
var existing = element[event_key];

Check failure on line 164 in packages/svelte/src/internal/client/dom/elements/attributes.js

View workflow job for this annotation

GitHub Actions / Lint

'existing' is already defined
if (existing != null) {
if (!Array.isArray(existing)) {
// @ts-ignore
element[event_key] = [existing];
}
existing.push(value);
} else {
// @ts-ignore
element[event_key] = value;
}
delegate([event_name]);
}
}
Expand Down

0 comments on commit b8c32f5

Please sign in to comment.