All files / src/internal/client/dom/blocks html.js

98.16% Statements 107/109
96.66% Branches 29/30
100% Functions 3/3
98.03% Lines 100/102

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 100 101 102 1032x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 129x 129x 129x 129x 126x 227x 120x 120x 120x 227x 129x     129x 2x 2x 2x 2x 2x 2x 2x 2x 89x 89x 89x 89x 155x 155x 155x 155x 155x 129x 129x 155x 155x 155x 89x 89x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 155x 155x 113x 113x 155x 113x 113x 113x 113x 113x 113x 155x 14x 14x 113x 142x 100x 100x 100x 84x 84x 100x 100x 13x 13x 13x 124x 1x 5x 5x 124x 12x 12x 13x 13x 13x 13x 13x 13x 13x  
import { derived } from '../../reactivity/deriveds.js';
import { render_effect } from '../../reactivity/effects.js';
import { current_effect, get } from '../../runtime.js';
import { is_array } from '../../utils.js';
import { hydrate_nodes, hydrating } from '../hydration.js';
import { create_fragment_from_html, remove } from '../reconciler.js';
import { push_template_node } from '../template.js';
 
/**
 * @param {import('#client').Effect} effect
 * @param {(Element | Comment | Text)[]} to_remove
 * @returns {void}
 */
function remove_from_parent_effect(effect, to_remove) {
	const dom = effect.dom;
 
	if (is_array(dom)) {
		for (let i = dom.length - 1; i >= 0; i--) {
			if (to_remove.includes(dom[i])) {
				dom.splice(i, 1);
				break;
			}
		}
	} else if (dom !== null && to_remove.includes(dom)) {
		effect.dom = null;
	}
}
 
/**
 * @param {Element | Text | Comment} anchor
 * @param {() => string} get_value
 * @param {boolean} svg
 * @returns {void}
 */
export function html(anchor, get_value, svg) {
	const parent_effect = anchor.parentNode !== current_effect?.dom ? current_effect : null;
	let value = derived(get_value);
 
	render_effect(() => {
		var dom = html_to_dom(anchor, parent_effect, get(value), svg);
 
		if (dom) {
			return () => {
				if (parent_effect !== null) {
					remove_from_parent_effect(parent_effect, is_array(dom) ? dom : [dom]);
				}
				remove(dom);
			};
		}
	});
}
 
/**
 * Creates the content for a `@html` tag from its string value,
 * inserts it before the target anchor and returns the new nodes.
 * @template V
 * @param {Element | Text | Comment} target
 * @param {import('#client').Effect | null} effect
 * @param {V} value
 * @param {boolean} svg
 * @returns {Element | Comment | (Element | Comment | Text)[]}
 */
function html_to_dom(target, effect, value, svg) {
	if (hydrating) return hydrate_nodes;
 
	var html = value + '';
	if (svg) html = `<svg>${html}</svg>`;
 
	// Don't use create_fragment_with_script_from_html here because that would mean script tags are executed.
	// @html is basically `.innerHTML = ...` and that doesn't execute scripts either due to security reasons.
	/** @type {DocumentFragment | Element} */
	var node = create_fragment_from_html(html);
 
	if (svg) {
		node = /** @type {Element} */ (node.firstChild);
	}
 
	if (node.childNodes.length === 1) {
		var child = /** @type {Text | Element | Comment} */ (node.firstChild);
		target.before(child);
		if (effect !== null) {
			push_template_node(child, effect);
		}
		return child;
	}
 
	var nodes = /** @type {Array<Text | Element | Comment>} */ ([...node.childNodes]);
 
	if (svg) {
		while (node.firstChild) {
			target.before(node.firstChild);
		}
	} else {
		target.before(node);
	}
 
	if (effect !== null) {
		push_template_node(nodes, effect);
	}
 
	return nodes;
}