All files / src/internal/client/dom/elements actions.js

96.07% Statements 98/102
96.29% Branches 26/27
100% Functions 3/3
95.95% Lines 95/99

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 1002x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 98x 98x 98x 98x 98x 98x 98x 98x 98x 98x 363x 363x 363x 363x 307x 307x 307x 307x 278x 11x 11x 11x 278x 307x 29x 29x 29x 363x 56x 56x 98x 98x 98x 98x 98x 98x 98x 98x 47x 47x 47x 47x 10x 10x 10x 10x 10x         10x 10x 10x 10x 10x 47x 37x 37x 98x 98x 98x 98x 98x 18x 18x 18x 40x 40x 40x 40x 40x 40x 40x 40x 22x 22x 18x 18x 18x 18x 98x 98x 48x 48x 98x 98x  
import { DelegatedEvents } from '../../../../constants.js';
import { effect, render_effect } from '../../reactivity/effects.js';
import { deep_read_state, untrack } from '../../runtime.js';
 
/**
 * @template P
 * @param {Element} dom
 * @param {(dom: Element, value?: P) => import('#client').ActionPayload<P>} action
 * @param {() => P} [get_value]
 * @returns {void}
 */
export function action(dom, action, get_value) {
	effect(() => {
		var addEventListener = dom.addEventListener;
		var removeEventListener = dom.removeEventListener;
 
		/**
		 * @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);
			}
		};
 
		/**
		 * @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);
			}
		};
 
		var payload = untrack(() => action(dom, get_value?.()) || {});
 
		if (get_value && payload?.update) {
			var inited = false;
 
			render_effect(() => {
				var value = get_value();
 
				// Action's update method is coarse-grained, i.e. when anything in the passed value changes, update.
				// This works in legacy mode because of mutable_source being updated as a whole, but when using $state
				// together with actions and mutation, it wouldn't notice the change without a deep read.
				deep_read_state(value);
 
				if (inited) {
					/** @type {Function} */ (payload.update)(value);
				}
			});
 
			inited = true;
		}
 
		if (payload?.destroy) {
			return () => /** @type {Function} */ (payload.destroy)();
		}
	});
}