fix: use operations functions everywhere

pull/18058/head
paoloricciuti 5 months ago
parent ff5e835bf0
commit f91c2fd6d8

@ -69,7 +69,6 @@ export const STALE_REACTION = new (class StaleReactionError extends Error {
message = 'The reaction that called `getAbortSignal()` was re-run or destroyed'; message = 'The reaction that called `getAbortSignal()` was re-run or destroyed';
})(); })();
// TODO: DOM access
export const IS_XHTML = export const IS_XHTML =
// We gotta write it like this because after downleveling the pure comment may end up in the wrong location // We gotta write it like this because after downleveling the pure comment may end up in the wrong location
!!globalThis.document?.contentType && !!globalThis.document?.contentType &&

@ -1,3 +1,5 @@
import { remove_node } from '../dom/operations';
/** @type {Map<String, Set<HTMLStyleElement>>} */ /** @type {Map<String, Set<HTMLStyleElement>>} */
var all_styles = new Map(); var all_styles = new Map();
@ -24,7 +26,7 @@ export function cleanup_styles(hash) {
if (!styles) return; if (!styles) return;
for (const style of styles) { for (const style of styles) {
style.remove(); remove_node(style);
} }
all_styles.delete(hash); all_styles.delete(hash);

@ -3,6 +3,7 @@ import { COMMENT_NODE, DOCUMENT_FRAGMENT_NODE, ELEMENT_NODE } from '#client/cons
import { HYDRATION_END, HYDRATION_START } from '../../../constants.js'; import { HYDRATION_END, HYDRATION_START } from '../../../constants.js';
import { hydrating } from '../dom/hydration.js'; import { hydrating } from '../dom/hydration.js';
import { dev_stack } from '../context.js'; import { dev_stack } from '../context.js';
import { get_first_child, get_next_sibling, get_node_value, node_type } from '../dom/operations.js';
/** /**
* @param {any} fn * @param {any} fn
@ -14,7 +15,12 @@ export function add_locations(fn, filename, locations) {
return (/** @type {any[]} */ ...args) => { return (/** @type {any[]} */ ...args) => {
const dom = fn(...args); const dom = fn(...args);
var node = hydrating ? dom : dom.nodeType === DOCUMENT_FRAGMENT_NODE ? dom.firstChild : dom; // check here
var node = hydrating
? dom
: node_type(dom) === DOCUMENT_FRAGMENT_NODE
? get_first_child(dom)
: dom;
assign_locations(node, filename, locations); assign_locations(node, filename, locations);
return dom; return dom;
@ -28,13 +34,14 @@ export function add_locations(fn, filename, locations) {
*/ */
function assign_location(element, filename, location) { function assign_location(element, filename, location) {
// @ts-expect-error // @ts-expect-error
// TODO RENDERER: what if the element is not an object?
element.__svelte_meta = { element.__svelte_meta = {
parent: dev_stack, parent: dev_stack,
loc: { file: filename, line: location[0], column: location[1] } loc: { file: filename, line: location[0], column: location[1] }
}; };
if (location[2]) { if (location[2]) {
assign_locations(element.firstChild, filename, location[2]); assign_locations(get_first_child(element), filename, location[2]);
} }
} }
@ -48,16 +55,17 @@ function assign_locations(node, filename, locations) {
var depth = 0; var depth = 0;
while (node && i < locations.length) { while (node && i < locations.length) {
if (hydrating && node.nodeType === COMMENT_NODE) { if (hydrating && node_type(node) === COMMENT_NODE) {
var comment = /** @type {Comment} */ (node); var comment = /** @type {Comment} */ (node);
if (comment.data[0] === HYDRATION_START) depth += 1; const data = get_node_value(comment) ?? '';
else if (comment.data[0] === HYDRATION_END) depth -= 1; if (data[0] === HYDRATION_START) depth += 1;
else if (data[0] === HYDRATION_END) depth -= 1;
} }
if (depth === 0 && node.nodeType === ELEMENT_NODE) { if (depth === 0 && node_type(node) === ELEMENT_NODE) {
assign_location(/** @type {Element} */ (node), filename, locations[i++]); assign_location(/** @type {Element} */ (node), filename, locations[i++]);
} }
node = node.nextSibling; node = get_next_sibling(node);
} }
} }

@ -4,6 +4,7 @@ import * as e from '../errors.js';
* @param {...(()=>any)[]} args * @param {...(()=>any)[]} args
*/ */
export function validate_snippet_args(anchor, ...args) { export function validate_snippet_args(anchor, ...args) {
// TODO RENDERER: don't emit validate args for custom renderers
if (typeof anchor !== 'object' || !(anchor instanceof Node)) { if (typeof anchor !== 'object' || !(anchor instanceof Node)) {
e.invalid_snippet_arguments(); e.invalid_snippet_arguments();
} }

@ -15,6 +15,7 @@ import { is_runes } from '../../context.js';
import { Batch, current_batch, flushSync, is_flushing_sync } from '../../reactivity/batch.js'; import { Batch, current_batch, flushSync, is_flushing_sync } from '../../reactivity/batch.js';
import { BranchManager } from './branches.js'; import { BranchManager } from './branches.js';
import { capture, unset_context } from '../../reactivity/async.js'; import { capture, unset_context } from '../../reactivity/async.js';
import { get_node_value } from '../operations.js';
const PENDING = 0; const PENDING = 0;
const THEN = 1; const THEN = 1;
@ -57,7 +58,8 @@ export function await_block(node, get_input, pending_fn, then_fn, catch_fn) {
/** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */ /** Whether or not there was a hydration mismatch. Needs to be a `let` or else it isn't treeshaken out */
// @ts-ignore coercing `node` to a `Comment` causes TypeScript and Prettier to fight // @ts-ignore coercing `node` to a `Comment` causes TypeScript and Prettier to fight
let mismatch = hydrating && is_promise(input) === (node.data === HYDRATION_START_ELSE); let mismatch =
hydrating && is_promise(input) === (get_node_value(node) === HYDRATION_START_ELSE);
if (mismatch) { if (mismatch) {
// Hydration mismatch: remove everything inside the anchor and start fresh // Hydration mismatch: remove everything inside the anchor and start fresh

@ -1,6 +1,6 @@
import { render_effect } from '../../reactivity/effects.js'; import { render_effect } from '../../reactivity/effects.js';
import { hydrating, set_hydrate_node } from '../hydration.js'; import { hydrating, set_hydrate_node } from '../hydration.js';
import { get_first_child } from '../operations.js'; import { get_first_child, style_set_property, style_remove_property } from '../operations.js';
/** /**
* @param {HTMLDivElement | SVGGElement} element * @param {HTMLDivElement | SVGGElement} element
@ -19,9 +19,9 @@ export function css_props(element, get_styles) {
var value = styles[key]; var value = styles[key];
if (value) { if (value) {
element.style.setProperty(key, value); style_set_property(/** @type {HTMLElement} */ (element), key, value);
} else { } else {
element.style.removeProperty(key); style_remove_property(/** @type {HTMLElement} */ (element), key);
} }
} }
}); });

@ -14,7 +14,17 @@ import * as w from '../../warnings.js';
import { hash, sanitize_location } from '../../../../utils.js'; import { hash, sanitize_location } from '../../../../utils.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { dev_current_component_function } from '../../context.js'; import { dev_current_component_function } from '../../context.js';
import { create_element, get_first_child, get_next_sibling } from '../operations.js'; import {
create_element,
get_first_child,
get_next_sibling,
get_last_child,
set_inner_html,
insert_before,
get_parent_node,
node_type,
get_node_value
} from '../operations.js';
import { active_effect } from '../../runtime.js'; import { active_effect } from '../../runtime.js';
import { COMMENT_NODE } from '#client/constants'; import { COMMENT_NODE } from '#client/constants';
@ -81,12 +91,12 @@ export function html(
// When @html is the only child, use innerHTML directly. // When @html is the only child, use innerHTML directly.
// This also handles contenteditable, where the user may delete the anchor comment. // This also handles contenteditable, where the user may delete the anchor comment.
effect.nodes = null; effect.nodes = null;
parent_node.innerHTML = /** @type {string} */ (value); set_inner_html(parent_node, /** @type {string} */ (value));
if (value !== '') { if (value !== '') {
assign_nodes( assign_nodes(
/** @type {TemplateNode} */ (get_first_child(parent_node)), /** @type {TemplateNode} */ (get_first_child(parent_node)),
/** @type {TemplateNode} */ (parent_node.lastChild) /** @type {TemplateNode} */ (get_last_child(parent_node))
); );
} }
@ -103,16 +113,13 @@ export function html(
if (hydrating) { if (hydrating) {
// We're deliberately not trying to repair mismatches between server and client, // We're deliberately not trying to repair mismatches between server and client,
// as it's costly and error-prone (and it's an edge case to have a mismatch anyway) // as it's costly and error-prone (and it's an edge case to have a mismatch anyway)
var hash = /** @type {Comment} */ (hydrate_node).data; var hash = get_node_value(hydrate_node);
/** @type {TemplateNode | null} */ /** @type {TemplateNode | null} */
var next = hydrate_next(); var next = hydrate_next();
var last = next; var last = next;
while ( while (next !== null && (node_type(next) !== COMMENT_NODE || get_node_value(next) !== '')) {
next !== null &&
(next.nodeType !== COMMENT_NODE || /** @type {Comment} */ (next).data !== '')
) {
last = next; last = next;
next = get_next_sibling(next); next = get_next_sibling(next);
} }
@ -123,7 +130,7 @@ export function html(
} }
if (DEV && !skip_warning) { if (DEV && !skip_warning) {
check_hash(/** @type {Element} */ (next.parentNode), hash, value); check_hash(/** @type {Element} */ (get_parent_node(next)), hash, value);
} }
assign_nodes(hydrate_node, last); assign_nodes(hydrate_node, last);
@ -139,22 +146,22 @@ export function html(
var wrapper = /** @type {HTMLTemplateElement | SVGElement | MathMLElement} */ ( var wrapper = /** @type {HTMLTemplateElement | SVGElement | MathMLElement} */ (
create_element(svg ? 'svg' : mathml ? 'math' : 'template', ns) create_element(svg ? 'svg' : mathml ? 'math' : 'template', ns)
); );
wrapper.innerHTML = /** @type {any} */ (value); set_inner_html(wrapper, /** @type {any} */ (value));
/** @type {DocumentFragment | Element} */ /** @type {DocumentFragment | Element} */
var node = svg || mathml ? wrapper : /** @type {HTMLTemplateElement} */ (wrapper).content; var node = svg || mathml ? wrapper : /** @type {HTMLTemplateElement} */ (wrapper).content;
assign_nodes( assign_nodes(
/** @type {TemplateNode} */ (get_first_child(node)), /** @type {TemplateNode} */ (get_first_child(node)),
/** @type {TemplateNode} */ (node.lastChild) /** @type {TemplateNode} */ (get_last_child(node))
); );
if (svg || mathml) { if (svg || mathml) {
while (get_first_child(node)) { while (get_first_child(node)) {
anchor.before(/** @type {TemplateNode} */ (get_first_child(node))); insert_before(anchor, /** @type {TemplateNode} */ (get_first_child(node)));
} }
} else { } else {
anchor.before(node); insert_before(anchor, node);
} }
}); });
} }

@ -13,7 +13,7 @@ import { assign_nodes } from '../template.js';
import * as w from '../../warnings.js'; import * as w from '../../warnings.js';
import * as e from '../../errors.js'; import * as e from '../../errors.js';
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { get_first_child, get_next_sibling } from '../operations.js'; import { get_first_child, get_next_sibling, insert_before, node_type } from '../operations.js';
import { prevent_snippet_stringification } from '../../../shared/validate.js'; import { prevent_snippet_stringification } from '../../../shared/validate.js';
import { BranchManager } from './branches.js'; import { BranchManager } from './branches.js';
@ -71,6 +71,7 @@ export function wrap_snippet(component, fn) {
* @returns {Snippet<Params>} * @returns {Snippet<Params>}
*/ */
export function createRawSnippet(fn) { export function createRawSnippet(fn) {
// TODO RENDERER: throw an error for createRawSnippet in a non-browser environment, as it relies on DOM APIs
// @ts-expect-error the types are a lie // @ts-expect-error the types are a lie
return (/** @type {TemplateNode} */ anchor, /** @type {Getters<Params>} */ ...params) => { return (/** @type {TemplateNode} */ anchor, /** @type {Getters<Params>} */ ...params) => {
var snippet = fn(...params); var snippet = fn(...params);
@ -86,11 +87,11 @@ export function createRawSnippet(fn) {
var fragment = create_fragment_from_html(html); var fragment = create_fragment_from_html(html);
element = /** @type {Element} */ (get_first_child(fragment)); element = /** @type {Element} */ (get_first_child(fragment));
if (DEV && (get_next_sibling(element) !== null || element.nodeType !== ELEMENT_NODE)) { if (DEV && (get_next_sibling(element) !== null || node_type(element) !== ELEMENT_NODE)) {
w.invalid_raw_snippet_render(); w.invalid_raw_snippet_render();
} }
anchor.before(element); insert_before(anchor, element);
} }
const result = snippet.setup?.(element); const result = snippet.setup?.(element);

@ -7,7 +7,15 @@ import {
set_hydrate_node, set_hydrate_node,
set_hydrating set_hydrating
} from '../hydration.js'; } from '../hydration.js';
import { create_element, create_text, get_first_child } from '../operations.js'; import {
create_element,
create_text,
get_first_child,
append_child,
create_comment,
insert_before,
node_type
} from '../operations.js';
import { block, teardown } from '../../reactivity/effects.js'; import { block, teardown } from '../../reactivity/effects.js';
import { set_should_intro } from '../../render.js'; import { set_should_intro } from '../../render.js';
import { active_effect } from '../../runtime.js'; import { active_effect } from '../../runtime.js';
@ -40,7 +48,7 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
/** @type {null | Element} */ /** @type {null | Element} */
var element = null; var element = null;
if (hydrating && hydrate_node.nodeType === ELEMENT_NODE) { if (hydrating && node_type(hydrate_node) === ELEMENT_NODE) {
element = /** @type {Element} */ (hydrate_node); element = /** @type {Element} */ (hydrate_node);
hydrate_next(); hydrate_next();
} }
@ -90,14 +98,14 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
if (render_fn) { if (render_fn) {
if (hydrating && is_raw_text_element(next_tag)) { if (hydrating && is_raw_text_element(next_tag)) {
// prevent hydration glitches // prevent hydration glitches
element.append(document.createComment('')); append_child(element, create_comment(''));
} }
// If hydrating, use the existing ssr comment as the anchor so that the // If hydrating, use the existing ssr comment as the anchor so that the
// inner open and close methods can pick up the existing nodes correctly // inner open and close methods can pick up the existing nodes correctly
var child_anchor = hydrating var child_anchor = hydrating
? get_first_child(element) ? get_first_child(element)
: element.appendChild(create_text()); : /** @type {Text} */ (append_child(element, create_text()));
if (hydrating) { if (hydrating) {
if (child_anchor === null) { if (child_anchor === null) {
@ -121,7 +129,7 @@ export function element(node, get_tag, is_svg, render_fn, get_namespace, locatio
// we do this after calling `render_fn` so that child effects don't override `nodes.end` // we do this after calling `render_fn` so that child effects don't override `nodes.end`
/** @type {Effect & { nodes: EffectNodes }} */ (active_effect).nodes.end = element; /** @type {Effect & { nodes: EffectNodes }} */ (active_effect).nodes.end = element;
anchor.before(element); insert_before(anchor, element);
} }
if (hydrating) { if (hydrating) {

@ -1,6 +1,14 @@
/** @import { TemplateNode } from '#client' */ /** @import { TemplateNode } from '#client' */
import { hydrate_node, hydrating, set_hydrate_node, set_hydrating } from '../hydration.js'; import { hydrate_node, hydrating, set_hydrate_node, set_hydrating } from '../hydration.js';
import { create_text, get_first_child, get_next_sibling } from '../operations.js'; import {
create_text,
get_first_child,
get_next_sibling,
remove_node,
append_child,
node_type,
get_node_value
} from '../operations.js';
import { block } from '../../reactivity/effects.js'; import { block } from '../../reactivity/effects.js';
import { COMMENT_NODE, EFFECT_PRESERVED, HEAD_EFFECT } from '#client/constants'; import { COMMENT_NODE, EFFECT_PRESERVED, HEAD_EFFECT } from '#client/constants';
@ -10,6 +18,7 @@ import { COMMENT_NODE, EFFECT_PRESERVED, HEAD_EFFECT } from '#client/constants';
* @returns {void} * @returns {void}
*/ */
export function head(hash, render_fn) { export function head(hash, render_fn) {
// TODO RENDERER: throw in case we are using custom renderers as this rely on the head element
// The head function may be called after the first hydration pass and ssr comment nodes may still be present, // The head function may be called after the first hydration pass and ssr comment nodes may still be present,
// therefore we need to skip that when we detect that we're not in hydration mode. // therefore we need to skip that when we detect that we're not in hydration mode.
let previous_hydrate_node = null; let previous_hydrate_node = null;
@ -27,7 +36,7 @@ export function head(hash, render_fn) {
// rendered in an arbitrary order — find one corresponding to this component // rendered in an arbitrary order — find one corresponding to this component
while ( while (
head_anchor !== null && head_anchor !== null &&
(head_anchor.nodeType !== COMMENT_NODE || /** @type {Comment} */ (head_anchor).data !== hash) (node_type(head_anchor) !== COMMENT_NODE || get_node_value(head_anchor) !== hash)
) { ) {
head_anchor = get_next_sibling(head_anchor); head_anchor = get_next_sibling(head_anchor);
} }
@ -38,14 +47,14 @@ export function head(hash, render_fn) {
set_hydrating(false); set_hydrating(false);
} else { } else {
var start = /** @type {TemplateNode} */ (get_next_sibling(head_anchor)); var start = /** @type {TemplateNode} */ (get_next_sibling(head_anchor));
head_anchor.remove(); // in case this component is repeated remove_node(/** @type {ChildNode} */ (head_anchor)); // in case this component is repeated
set_hydrate_node(start); set_hydrate_node(start);
} }
} }
if (!hydrating) { if (!hydrating) {
anchor = document.head.appendChild(create_text()); anchor = /** @type {Comment | Text} */ (append_child(document.head, create_text()));
} }
try { try {

@ -1,29 +1,39 @@
import { DEV } from 'esm-env'; import { DEV } from 'esm-env';
import { register_style } from '../dev/css.js'; import { register_style } from '../dev/css.js';
import { effect } from '../reactivity/effects.js'; import { effect } from '../reactivity/effects.js';
import { create_element } from './operations.js'; import {
create_element,
get_root_node,
query_selector,
append_child,
set_text_content
} from './operations.js';
/** /**
* @param {Node} anchor * @param {Node} anchor
* @param {{ hash: string, code: string }} css * @param {{ hash: string, code: string }} css
*/ */
export function append_styles(anchor, css) { export function append_styles(anchor, css) {
// TODO RENDERER: disallow css inject with custom renderer?
// Use `queue_micro_task` to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results // Use `queue_micro_task` to ensure `anchor` is in the DOM, otherwise getRootNode() will yield wrong results
effect(() => { effect(() => {
var root = anchor.getRootNode(); var root = get_root_node(anchor);
// TODO: DOM access
var target = /** @type {ShadowRoot} */ (root).host var target = /** @type {ShadowRoot} */ (root).host
? /** @type {ShadowRoot} */ (root) ? /** @type {ShadowRoot} */ (root)
: /** @type {Document} */ (root).head ?? /** @type {Document} */ (root.ownerDocument).head; : // TODO: DOM access
/** @type {Document} */ (root).head ?? /** @type {Document} */ (root.ownerDocument).head;
// Always querying the DOM is roughly the same perf as additionally checking for presence in a map first assuming // Always querying the DOM is roughly the same perf as additionally checking for presence in a map first assuming
// that you'll get cache hits half of the time, so we just always query the dom for simplicity and code savings. // that you'll get cache hits half of the time, so we just always query the dom for simplicity and code savings.
if (!target.querySelector('#' + css.hash)) { if (!query_selector(/** @type {Element} */ (target), '#' + css.hash)) {
const style = create_element('style'); const style = create_element('style');
// TODO: DOM access
style.id = css.hash; style.id = css.hash;
style.textContent = css.code; set_text_content(style, css.code);
target.appendChild(style); append_child(target, style);
if (DEV) { if (DEV) {
register_style(css.hash, style); register_style(css.hash, style);

@ -1,5 +1,5 @@
import { hydrating } from '../hydration.js'; import { hydrating } from '../hydration.js';
import { clear_text_content, get_first_child } from '../operations.js'; import { clear_text_content, get_first_child, focus, add_event_listener } from '../operations.js';
import { queue_micro_task } from '../task.js'; import { queue_micro_task } from '../task.js';
/** /**
@ -14,7 +14,7 @@ export function autofocus(dom, value) {
queue_micro_task(() => { queue_micro_task(() => {
if (document.activeElement === body) { if (document.activeElement === body) {
dom.focus(); focus(dom);
} }
}); });
} }
@ -27,6 +27,7 @@ export function autofocus(dom, value) {
* @returns {void} * @returns {void}
*/ */
export function remove_textarea_child(dom) { export function remove_textarea_child(dom) {
// TODO RENDERER: never output in custom render mode
if (hydrating && get_first_child(dom) !== null) { if (hydrating && get_first_child(dom) !== null) {
clear_text_content(dom); clear_text_content(dom);
} }
@ -35,15 +36,19 @@ export function remove_textarea_child(dom) {
let listening_to_form_reset = false; let listening_to_form_reset = false;
export function add_form_reset_listener() { export function add_form_reset_listener() {
// TODO RENDERER: never output in custom render mode
if (!listening_to_form_reset) { if (!listening_to_form_reset) {
listening_to_form_reset = true; listening_to_form_reset = true;
document.addEventListener( // TODO: DOM access
add_event_listener(
document,
'reset', 'reset',
(evt) => { (evt) => {
// Needs to happen one tick later or else the dom properties of the form // Needs to happen one tick later or else the dom properties of the form
// elements have not updated to their reset values yet // elements have not updated to their reset values yet
Promise.resolve().then(() => { Promise.resolve().then(() => {
if (!evt.defaultPrevented) { if (!evt.defaultPrevented) {
// TODO: DOM access
for (const e of /**@type {HTMLFormElement} */ (evt.target).elements) { for (const e of /**@type {HTMLFormElement} */ (evt.target).elements) {
// @ts-expect-error // @ts-expect-error
e.__on_r?.(); e.__on_r?.();

@ -8,7 +8,7 @@ import {
HYDRATION_START_ELSE HYDRATION_START_ELSE
} from '../../../constants.js'; } from '../../../constants.js';
import * as w from '../warnings.js'; import * as w from '../warnings.js';
import { get_next_sibling } from './operations.js'; import { get_next_sibling, get_node_value, node_type, remove_node } from './operations.js';
/** /**
* Use this variable to guard everything related to hydration code so it can be treeshaken out * Use this variable to guard everything related to hydration code so it can be treeshaken out
@ -89,8 +89,8 @@ export function skip_nodes(remove = true) {
var node = hydrate_node; var node = hydrate_node;
while (true) { while (true) {
if (node.nodeType === COMMENT_NODE) { if (node_type(node) === COMMENT_NODE) {
var data = /** @type {Comment} */ (node).data; var data = get_node_value(node) ?? '';
if (data === HYDRATION_END) { if (data === HYDRATION_END) {
if (depth === 0) return node; if (depth === 0) return node;
@ -106,7 +106,7 @@ export function skip_nodes(remove = true) {
} }
var next = /** @type {TemplateNode} */ (get_next_sibling(node)); var next = /** @type {TemplateNode} */ (get_next_sibling(node));
if (remove) node.remove(); if (remove) remove_node(node);
node = next; node = next;
} }
} }
@ -116,10 +116,10 @@ export function skip_nodes(remove = true) {
* @param {TemplateNode} node * @param {TemplateNode} node
*/ */
export function read_hydration_instruction(node) { export function read_hydration_instruction(node) {
if (!node || node.nodeType !== COMMENT_NODE) { if (!node || node_type(node) !== COMMENT_NODE) {
w.hydration_mismatch(); w.hydration_mismatch();
throw HYDRATION_ERROR; throw HYDRATION_ERROR;
} }
return /** @type {Comment} */ (node).data; return get_node_value(node) ?? '';
} }

@ -1,4 +1,4 @@
import { create_element } from './operations.js'; import { create_element, set_inner_html } from './operations.js';
const policy = const policy =
// We gotta write it like this because after downleveling the pure comment may end up in the wrong location // We gotta write it like this because after downleveling the pure comment may end up in the wrong location
@ -20,6 +20,6 @@ export function create_trusted_html(html) {
*/ */
export function create_fragment_from_html(html) { export function create_fragment_from_html(html) {
var elem = create_element('template'); var elem = create_element('template');
elem.innerHTML = create_trusted_html(html.replaceAll('<!>', '<!---->')); // XHTML compliance set_inner_html(elem, create_trusted_html(html.replaceAll('<!>', '<!---->'))); // XHTML compliance
return elem.content; return elem.content;
} }

@ -208,7 +208,7 @@ function fragment_from_tree(structure, ns) {
if (children.length > 0) { if (children.length > 0) {
var target = var target =
node_name(element) === TEMPLATE_TAG node_name(element) === TEMPLATE_TAG
? // TODO: DOM access ? // this is safe for custom renderers because the name will never be `template` due to how `node_name` works
/** @type {HTMLTemplateElement} */ (element).content /** @type {HTMLTemplateElement} */ (element).content
: element; : element;
@ -283,6 +283,7 @@ export function from_tree(structure, flags) {
* @param {() => Element | DocumentFragment} fn * @param {() => Element | DocumentFragment} fn
*/ */
export function with_script(fn) { export function with_script(fn) {
// TODO RENDERER: never emit this for custom renderers
return () => run_scripts(fn()); return () => run_scripts(fn());
} }
@ -293,6 +294,7 @@ export function with_script(fn) {
* @returns {Node | Node[]} * @returns {Node | Node[]}
*/ */
function run_scripts(node) { function run_scripts(node) {
// this should be custom renderer safe since we never emit `with_script` in that case
// scripts were SSR'd, in which case they will run // scripts were SSR'd, in which case they will run
if (hydrating) return node; if (hydrating) return node;
@ -307,12 +309,11 @@ function run_scripts(node) {
for (const script of scripts) { for (const script of scripts) {
const clone = create_element('script'); const clone = create_element('script');
// TODO: DOM access
for (var attribute of script.attributes) { for (var attribute of script.attributes) {
set_attribute(clone, attribute.name, attribute.value); set_attribute(clone, attribute.name, attribute.value);
} }
// TODO: DOM access
set_text_content(clone, script.textContent ?? ''); set_text_content(clone, script.textContent ?? '');
// The script has changed - if it's at the edges, the effect now points at dead nodes // The script has changed - if it's at the edges, the effect now points at dead nodes

@ -6,7 +6,15 @@ import {
create_text, create_text,
get_first_child, get_first_child,
get_next_sibling, get_next_sibling,
init_operations init_operations,
append_child,
add_event_listener,
remove_event_listener,
get_parent_node,
remove_child,
set_node_value,
get_node_value,
node_type
} from './dom/operations.js'; } from './dom/operations.js';
import { HYDRATION_END, HYDRATION_ERROR, HYDRATION_START } from '../../constants.js'; import { HYDRATION_END, HYDRATION_ERROR, HYDRATION_START } from '../../constants.js';
import { active_effect } from './runtime.js'; import { active_effect } from './runtime.js';
@ -47,10 +55,10 @@ export function set_text(text, value) {
// For objects, we apply string coercion (which might make things like $state array references in the template reactive) before diffing // For objects, we apply string coercion (which might make things like $state array references in the template reactive) before diffing
var str = value == null ? '' : typeof value === 'object' ? `${value}` : value; var str = value == null ? '' : typeof value === 'object' ? `${value}` : value;
// @ts-expect-error // @ts-expect-error
if (str !== (text.__t ??= text.nodeValue)) { if (str !== (text.__t ??= get_node_value(text))) {
// @ts-expect-error // @ts-expect-error
text.__t = str; text.__t = str;
text.nodeValue = `${str}`; set_node_value(text, `${str}`);
} }
} }
@ -105,7 +113,7 @@ export function hydrate(component, options) {
while ( while (
anchor && anchor &&
(anchor.nodeType !== COMMENT_NODE || /** @type {Comment} */ (anchor).data !== HYDRATION_START) (node_type(anchor) !== COMMENT_NODE || get_node_value(anchor) !== HYDRATION_START)
) { ) {
anchor = get_next_sibling(anchor); anchor = get_next_sibling(anchor);
} }
@ -171,7 +179,7 @@ function _mount(
var component = undefined; var component = undefined;
var unmount = component_root(() => { var unmount = component_root(() => {
var anchor_node = anchor ?? target.appendChild(create_text()); var anchor_node = anchor ?? /** @type {Text} */ (append_child(target, create_text()));
boundary( boundary(
/** @type {TemplateNode} */ (anchor_node), /** @type {TemplateNode} */ (anchor_node),
@ -202,8 +210,8 @@ function _mount(
if ( if (
hydrate_node === null || hydrate_node === null ||
hydrate_node.nodeType !== COMMENT_NODE || node_type(hydrate_node) !== COMMENT_NODE ||
/** @type {Comment} */ (hydrate_node).data !== HYDRATION_END get_node_value(hydrate_node) !== HYDRATION_END
) { ) {
w.hydration_mismatch(); w.hydration_mismatch();
throw HYDRATION_ERROR; throw HYDRATION_ERROR;
@ -246,7 +254,7 @@ function _mount(
var count = counts.get(event_name); var count = counts.get(event_name);
if (count === undefined) { if (count === undefined) {
node.addEventListener(event_name, handle_event_propagation, { passive }); add_event_listener(node, event_name, handle_event_propagation, { passive });
counts.set(event_name, 1); counts.set(event_name, 1);
} else { } else {
counts.set(event_name, count + 1); counts.set(event_name, count + 1);
@ -265,7 +273,7 @@ function _mount(
var count = /** @type {number} */ (counts.get(event_name)); var count = /** @type {number} */ (counts.get(event_name));
if (--count == 0) { if (--count == 0) {
node.removeEventListener(event_name, handle_event_propagation); remove_event_listener(node, event_name, handle_event_propagation);
counts.delete(event_name); counts.delete(event_name);
if (counts.size === 0) { if (counts.size === 0) {
@ -280,7 +288,8 @@ function _mount(
root_event_handles.delete(event_handle); root_event_handles.delete(event_handle);
if (anchor_node !== anchor) { if (anchor_node !== anchor) {
anchor_node.parentNode?.removeChild(anchor_node); var parent = get_parent_node(anchor_node);
if (parent) remove_child(parent, /** @type {ChildNode} */ (anchor_node));
} }
}; };
}); });

Loading…
Cancel
Save