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

98.21% Statements 110/112
94.44% Branches 34/36
100% Functions 3/3
98.09% Lines 103/105

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 103 104 105 1062x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 111x 111x 111x 111x 108x 190x 102x 102x 102x 190x 111x     111x 2x 2x 2x 2x 2x 2x 2x 2x 2x 77x 77x 77x 77x 137x 137x 137x 137x 137x 111x 111x 137x 137x 137x 77x 77x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 137x 137x 98x 98x 137x 85x 98x 98x 98x 98x 98x 98x 137x 13x 13x 98x 128x 85x 85x 85x 69x 69x 85x 85x 13x 13x 13x 137x 1x 5x 5x 126x 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
 * @param {boolean} mathml
 * @returns {void}
 */
export function html(anchor, get_value, svg, mathml) {
	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, mathml);
 
		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
 * @param {boolean} mathml
 * @returns {Element | Comment | (Element | Comment | Text)[]}
 */
function html_to_dom(target, effect, value, svg, mathml) {
	if (hydrating) return hydrate_nodes;
 
	var html = value + '';
	if (svg) html = `<svg>${html}</svg>`;
	else if (mathml) html = `<math>${html}</math>`;
 
	// 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 || mathml) {
		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 || mathml) {
		while (node.firstChild) {
			target.before(node.firstChild);
		}
	} else {
		target.before(node);
	}
 
	if (effect !== null) {
		push_template_node(nodes, effect);
	}
 
	return nodes;
}