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

98.21% Statements 110/112
97.22% Branches 35/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 133x 133x 133x 133x 130x 248x 124x 124x 124x 248x 133x     133x 2x 2x 2x 2x 2x 2x 2x 2x 2x 93x 93x 93x 93x 159x 159x 159x 159x 159x 133x 133x 159x 159x 159x 93x 93x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 159x 159x 115x 115x 159x 101x 115x 115x 115x 115x 115x 115x 159x 15x 15x 115x 145x 102x 102x 102x 86x 86x 102x 102x 13x 13x 13x 159x 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;
}