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 131x 131x 131x 131x 128x 237x 122x 122x 122x 237x 131x     131x 2x 2x 2x 2x 2x 2x 2x 2x 2x 91x 91x 91x 91x 157x 157x 157x 157x 157x 131x 131x 157x 157x 157x 91x 91x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 157x 157x 114x 114x 157x 100x 114x 114x 114x 114x 114x 114x 157x 15x 15x 114x 143x 101x 101x 101x 85x 85x 101x 101x 13x 13x 13x 157x 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
 * @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;
}