dom.js
   1  import { contenteditable_truthy_values, has_prop } from './utils.js';
   2  
   3  import { ResizeObserverSingleton } from './ResizeObserverSingleton.js';
   4  
   5  // Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM
   6  // at the end of hydration without touching the remaining nodes.
   7  let is_hydrating = false;
   8  
   9  /**
  10   * @returns {void}
  11   */
  12  export function start_hydrating() {
  13  	is_hydrating = true;
  14  }
  15  
  16  /**
  17   * @returns {void}
  18   */
  19  export function end_hydrating() {
  20  	is_hydrating = false;
  21  }
  22  
  23  /**
  24   * @param {number} low
  25   * @param {number} high
  26   * @param {(index: number) => number} key
  27   * @param {number} value
  28   * @returns {number}
  29   */
  30  function upper_bound(low, high, key, value) {
  31  	// Return first index of value larger than input value in the range [low, high)
  32  	while (low < high) {
  33  		const mid = low + ((high - low) >> 1);
  34  		if (key(mid) <= value) {
  35  			low = mid + 1;
  36  		} else {
  37  			high = mid;
  38  		}
  39  	}
  40  	return low;
  41  }
  42  
  43  /**
  44   * @param {NodeEx} target
  45   * @returns {void}
  46   */
  47  function init_hydrate(target) {
  48  	if (target.hydrate_init) return;
  49  	target.hydrate_init = true;
  50  	// We know that all children have claim_order values since the unclaimed have been detached if target is not <head>
  51  
  52  	let children = /** @type {ArrayLike<NodeEx2>} */ (target.childNodes);
  53  	// If target is <head>, there may be children without claim_order
  54  	if (target.nodeName === 'HEAD') {
  55  		const my_children = [];
  56  		for (let i = 0; i < children.length; i++) {
  57  			const node = children[i];
  58  			if (node.claim_order !== undefined) {
  59  				my_children.push(node);
  60  			}
  61  		}
  62  		children = my_children;
  63  	}
  64  	/*
  65  	 * Reorder claimed children optimally.
  66  	 * We can reorder claimed children optimally by finding the longest subsequence of
  67  	 * nodes that are already claimed in order and only moving the rest. The longest
  68  	 * subsequence of nodes that are claimed in order can be found by
  69  	 * computing the longest increasing subsequence of .claim_order values.
  70  	 *
  71  	 * This algorithm is optimal in generating the least amount of reorder operations
  72  	 * possible.
  73  	 *
  74  	 * Proof:
  75  	 * We know that, given a set of reordering operations, the nodes that do not move
  76  	 * always form an increasing subsequence, since they do not move among each other
  77  	 * meaning that they must be already ordered among each other. Thus, the maximal
  78  	 * set of nodes that do not move form a longest increasing subsequence.
  79  	 */
  80  	// Compute longest increasing subsequence
  81  	// m: subsequence length j => index k of smallest value that ends an increasing subsequence of length j
  82  	const m = new Int32Array(children.length + 1);
  83  	// Predecessor indices + 1
  84  	const p = new Int32Array(children.length);
  85  	m[0] = -1;
  86  	let longest = 0;
  87  	for (let i = 0; i < children.length; i++) {
  88  		const current = children[i].claim_order;
  89  		// Find the largest subsequence length such that it ends in a value less than our current value
  90  		// upper_bound returns first greater value, so we subtract one
  91  		// with fast path for when we are on the current longest subsequence
  92  		const seq_len =
  93  			(longest > 0 && children[m[longest]].claim_order <= current
  94  				? longest + 1
  95  				: upper_bound(1, longest, (idx) => children[m[idx]].claim_order, current)) - 1;
  96  		p[i] = m[seq_len] + 1;
  97  		const new_len = seq_len + 1;
  98  		// We can guarantee that current is the smallest value. Otherwise, we would have generated a longer sequence.
  99  		m[new_len] = i;
 100  		longest = Math.max(new_len, longest);
 101  	}
 102  	// The longest increasing subsequence of nodes (initially reversed)
 103  
 104  	/**
 105  	 * @type {NodeEx2[]}
 106  	 */
 107  	const lis = [];
 108  	// The rest of the nodes, nodes that will be moved
 109  
 110  	/**
 111  	 * @type {NodeEx2[]}
 112  	 */
 113  	const to_move = [];
 114  	let last = children.length - 1;
 115  	for (let cur = m[longest] + 1; cur != 0; cur = p[cur - 1]) {
 116  		lis.push(children[cur - 1]);
 117  		for (; last >= cur; last--) {
 118  			to_move.push(children[last]);
 119  		}
 120  		last--;
 121  	}
 122  	for (; last >= 0; last--) {
 123  		to_move.push(children[last]);
 124  	}
 125  	lis.reverse();
 126  	// We sort the nodes being moved to guarantee that their insertion order matches the claim order
 127  	to_move.sort((a, b) => a.claim_order - b.claim_order);
 128  	// Finally, we move the nodes
 129  	for (let i = 0, j = 0; i < to_move.length; i++) {
 130  		while (j < lis.length && to_move[i].claim_order >= lis[j].claim_order) {
 131  			j++;
 132  		}
 133  		const anchor = j < lis.length ? lis[j] : null;
 134  		target.insertBefore(to_move[i], anchor);
 135  	}
 136  }
 137  
 138  /**
 139   * @param {Node} target
 140   * @param {Node} node
 141   * @returns {void}
 142   */
 143  export function append(target, node) {
 144  	target.appendChild(node);
 145  }
 146  
 147  /**
 148   * @param {Node} target
 149   * @param {string} style_sheet_id
 150   * @param {string} styles
 151   * @returns {void}
 152   */
 153  export function append_styles(target, style_sheet_id, styles) {
 154  	const append_styles_to = get_root_for_style(target);
 155  	if (!append_styles_to.getElementById(style_sheet_id)) {
 156  		const style = element('style');
 157  		style.id = style_sheet_id;
 158  		style.textContent = styles;
 159  		append_stylesheet(append_styles_to, style);
 160  	}
 161  }
 162  
 163  /**
 164   * @param {Node} node
 165   * @returns {ShadowRoot | Document}
 166   */
 167  export function get_root_for_style(node) {
 168  	if (!node) return document;
 169  	const root = node.getRootNode ? node.getRootNode() : node.ownerDocument;
 170  	if (root && /** @type {ShadowRoot} */ (root).host) {
 171  		return /** @type {ShadowRoot} */ (root);
 172  	}
 173  	return node.ownerDocument;
 174  }
 175  
 176  /**
 177   * @param {Node} node
 178   * @returns {CSSStyleSheet}
 179   */
 180  export function append_empty_stylesheet(node) {
 181  	const style_element = element('style');
 182  	// For transitions to work without 'style-src: unsafe-inline' Content Security Policy,
 183  	// these empty tags need to be allowed with a hash as a workaround until we move to the Web Animations API.
 184  	// Using the hash for the empty string (for an empty tag) works in all browsers except Safari.
 185  	// So as a workaround for the workaround, when we append empty style tags we set their content to /* empty */.
 186  	// The hash 'sha256-9OlNO0DNEeaVzHL4RZwCLsBHA8WBQ8toBp/4F5XV2nc=' will then work even in Safari.
 187  	style_element.textContent = '/* empty */';
 188  	append_stylesheet(get_root_for_style(node), style_element);
 189  	return style_element.sheet;
 190  }
 191  
 192  /**
 193   * @param {ShadowRoot | Document} node
 194   * @param {HTMLStyleElement} style
 195   * @returns {CSSStyleSheet}
 196   */
 197  function append_stylesheet(node, style) {
 198  	append(/** @type {Document} */ (node).head || node, style);
 199  	return style.sheet;
 200  }
 201  
 202  /**
 203   * @param {NodeEx} target
 204   * @param {NodeEx} node
 205   * @returns {void}
 206   */
 207  export function append_hydration(target, node) {
 208  	if (is_hydrating) {
 209  		init_hydrate(target);
 210  		if (
 211  			target.actual_end_child === undefined ||
 212  			(target.actual_end_child !== null && target.actual_end_child.parentNode !== target)
 213  		) {
 214  			target.actual_end_child = target.firstChild;
 215  		}
 216  		// Skip nodes of undefined ordering
 217  		while (target.actual_end_child !== null && target.actual_end_child.claim_order === undefined) {
 218  			target.actual_end_child = target.actual_end_child.nextSibling;
 219  		}
 220  		if (node !== target.actual_end_child) {
 221  			// We only insert if the ordering of this node should be modified or the parent node is not target
 222  			if (node.claim_order !== undefined || node.parentNode !== target) {
 223  				target.insertBefore(node, target.actual_end_child);
 224  			}
 225  		} else {
 226  			target.actual_end_child = node.nextSibling;
 227  		}
 228  	} else if (node.parentNode !== target || node.nextSibling !== null) {
 229  		target.appendChild(node);
 230  	}
 231  }
 232  
 233  /**
 234   * @param {Node} target
 235   * @param {Node} node
 236   * @param {Node} [anchor]
 237   * @returns {void}
 238   */
 239  export function insert(target, node, anchor) {
 240  	target.insertBefore(node, anchor || null);
 241  }
 242  
 243  /**
 244   * @param {NodeEx} target
 245   * @param {NodeEx} node
 246   * @param {NodeEx} [anchor]
 247   * @returns {void}
 248   */
 249  export function insert_hydration(target, node, anchor) {
 250  	if (is_hydrating && !anchor) {
 251  		append_hydration(target, node);
 252  	} else if (node.parentNode !== target || node.nextSibling != anchor) {
 253  		target.insertBefore(node, anchor || null);
 254  	}
 255  }
 256  
 257  /**
 258   * @param {Node} node
 259   * @returns {void}
 260   */
 261  export function detach(node) {
 262  	if (node.parentNode) {
 263  		node.parentNode.removeChild(node);
 264  	}
 265  }
 266  
 267  /**
 268   * @returns {void} */
 269  export function destroy_each(iterations, detaching) {
 270  	for (let i = 0; i < iterations.length; i += 1) {
 271  		if (iterations[i]) iterations[i].d(detaching);
 272  	}
 273  }
 274  
 275  /**
 276   * @template {keyof HTMLElementTagNameMap} K
 277   * @param {K} name
 278   * @returns {HTMLElementTagNameMap[K]}
 279   */
 280  export function element(name) {
 281  	return document.createElement(name);
 282  }
 283  
 284  /**
 285   * @template {keyof HTMLElementTagNameMap} K
 286   * @param {K} name
 287   * @param {string} is
 288   * @returns {HTMLElementTagNameMap[K]}
 289   */
 290  export function element_is(name, is) {
 291  	return document.createElement(name, { is });
 292  }
 293  
 294  /**
 295   * @template T
 296   * @template {keyof T} K
 297   * @param {T} obj
 298   * @param {K[]} exclude
 299   * @returns {Pick<T, Exclude<keyof T, K>>}
 300   */
 301  export function object_without_properties(obj, exclude) {
 302  	const target = /** @type {Pick<T, Exclude<keyof T, K>>} */ ({});
 303  	for (const k in obj) {
 304  		if (
 305  			has_prop(obj, k) &&
 306  			// @ts-ignore
 307  			exclude.indexOf(k) === -1
 308  		) {
 309  			// @ts-ignore
 310  			target[k] = obj[k];
 311  		}
 312  	}
 313  	return target;
 314  }
 315  
 316  /**
 317   * @template {keyof SVGElementTagNameMap} K
 318   * @param {K} name
 319   * @returns {SVGElement}
 320   */
 321  export function svg_element(name) {
 322  	return document.createElementNS('http://www.w3.org/2000/svg', name);
 323  }
 324  
 325  /**
 326   * @param {string} data
 327   * @returns {Text}
 328   */
 329  export function text(data) {
 330  	return document.createTextNode(data);
 331  }
 332  
 333  /**
 334   * @returns {Text} */
 335  export function space() {
 336  	return text(' ');
 337  }
 338  
 339  /**
 340   * @returns {Text} */
 341  export function empty() {
 342  	return text('');
 343  }
 344  
 345  /**
 346   * @param {string} content
 347   * @returns {Comment}
 348   */
 349  export function comment(content) {
 350  	return document.createComment(content);
 351  }
 352  
 353  /**
 354   * @param {EventTarget} node
 355   * @param {string} event
 356   * @param {EventListenerOrEventListenerObject} handler
 357   * @param {boolean | AddEventListenerOptions | EventListenerOptions} [options]
 358   * @returns {() => void}
 359   */
 360  export function listen(node, event, handler, options) {
 361  	node.addEventListener(event, handler, options);
 362  	return () => node.removeEventListener(event, handler, options);
 363  }
 364  
 365  /**
 366   * @returns {(event: any) => any} */
 367  export function prevent_default(fn) {
 368  	return function (event) {
 369  		event.preventDefault();
 370  		// @ts-ignore
 371  		return fn.call(this, event);
 372  	};
 373  }
 374  
 375  /**
 376   * @returns {(event: any) => any} */
 377  export function stop_propagation(fn) {
 378  	return function (event) {
 379  		event.stopPropagation();
 380  		// @ts-ignore
 381  		return fn.call(this, event);
 382  	};
 383  }
 384  
 385  /**
 386   * @returns {(event: any) => any} */
 387  export function stop_immediate_propagation(fn) {
 388  	return function (event) {
 389  		event.stopImmediatePropagation();
 390  		// @ts-ignore
 391  		return fn.call(this, event);
 392  	};
 393  }
 394  
 395  /**
 396   * @returns {(event: any) => void} */
 397  export function self(fn) {
 398  	return function (event) {
 399  		// @ts-ignore
 400  		if (event.target === this) fn.call(this, event);
 401  	};
 402  }
 403  
 404  /**
 405   * @returns {(event: any) => void} */
 406  export function trusted(fn) {
 407  	return function (event) {
 408  		// @ts-ignore
 409  		if (event.isTrusted) fn.call(this, event);
 410  	};
 411  }
 412  
 413  /**
 414   * @param {Element} node
 415   * @param {string} attribute
 416   * @param {string} [value]
 417   * @returns {void}
 418   */
 419  export function attr(node, attribute, value) {
 420  	if (value == null) node.removeAttribute(attribute);
 421  	else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value);
 422  }
 423  /**
 424   * List of attributes that should always be set through the attr method,
 425   * because updating them through the property setter doesn't work reliably.
 426   * In the example of `width`/`height`, the problem is that the setter only
 427   * accepts numeric values, but the attribute can also be set to a string like `50%`.
 428   * If this list becomes too big, rethink this approach.
 429   */
 430  const always_set_through_set_attribute = ['width', 'height'];
 431  
 432  /**
 433   * @param {Element & ElementCSSInlineStyle} node
 434   * @param {{ [x: string]: string }} attributes
 435   * @returns {void}
 436   */
 437  export function set_attributes(node, attributes) {
 438  	// @ts-ignore
 439  	const descriptors = Object.getOwnPropertyDescriptors(node.__proto__);
 440  	for (const key in attributes) {
 441  		if (attributes[key] == null) {
 442  			node.removeAttribute(key);
 443  		} else if (key === 'style') {
 444  			node.style.cssText = attributes[key];
 445  		} else if (key === '__value') {
 446  			/** @type {any} */ (node).value = node[key] = attributes[key];
 447  		} else if (
 448  			descriptors[key] &&
 449  			descriptors[key].set &&
 450  			always_set_through_set_attribute.indexOf(key) === -1
 451  		) {
 452  			node[key] = attributes[key];
 453  		} else {
 454  			attr(node, key, attributes[key]);
 455  		}
 456  	}
 457  }
 458  
 459  /**
 460   * @param {Element & ElementCSSInlineStyle} node
 461   * @param {{ [x: string]: string }} attributes
 462   * @returns {void}
 463   */
 464  export function set_svg_attributes(node, attributes) {
 465  	for (const key in attributes) {
 466  		attr(node, key, attributes[key]);
 467  	}
 468  }
 469  
 470  /**
 471   * @param {Record<string, unknown>} data_map
 472   * @returns {void}
 473   */
 474  export function set_custom_element_data_map(node, data_map) {
 475  	Object.keys(data_map).forEach((key) => {
 476  		set_custom_element_data(node, key, data_map[key]);
 477  	});
 478  }
 479  
 480  /**
 481   * @returns {void} */
 482  export function set_custom_element_data(node, prop, value) {
 483  	const lower = prop.toLowerCase(); // for backwards compatibility with existing behavior we do lowercase first
 484  	if (lower in node) {
 485  		node[lower] = typeof node[lower] === 'boolean' && value === '' ? true : value;
 486  	} else if (prop in node) {
 487  		node[prop] = typeof node[prop] === 'boolean' && value === '' ? true : value;
 488  	} else {
 489  		attr(node, prop, value);
 490  	}
 491  }
 492  
 493  /**
 494   * @param {string} tag
 495   */
 496  export function set_dynamic_element_data(tag) {
 497  	return /-/.test(tag) ? set_custom_element_data_map : set_attributes;
 498  }
 499  
 500  /**
 501   * @returns {void}
 502   */
 503  export function xlink_attr(node, attribute, value) {
 504  	node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
 505  }
 506  
 507  /**
 508   * @param {HTMLElement} node
 509   * @returns {string}
 510   */
 511  export function get_svelte_dataset(node) {
 512  	return node.dataset.svelteH;
 513  }
 514  
 515  /**
 516   * @returns {unknown[]} */
 517  export function get_binding_group_value(group, __value, checked) {
 518  	const value = new Set();
 519  	for (let i = 0; i < group.length; i += 1) {
 520  		if (group[i].checked) value.add(group[i].__value);
 521  	}
 522  	if (!checked) {
 523  		value.delete(__value);
 524  	}
 525  	return Array.from(value);
 526  }
 527  
 528  /**
 529   * @param {HTMLInputElement[]} group
 530   * @returns {{ p(...inputs: HTMLInputElement[]): void; r(): void; }}
 531   */
 532  export function init_binding_group(group) {
 533  	/**
 534  	 * @type {HTMLInputElement[]} */
 535  	let _inputs;
 536  	return {
 537  		/* push */ p(...inputs) {
 538  			_inputs = inputs;
 539  			_inputs.forEach((input) => group.push(input));
 540  		},
 541  		/* remove */ r() {
 542  			_inputs.forEach((input) => group.splice(group.indexOf(input), 1));
 543  		}
 544  	};
 545  }
 546  
 547  /**
 548   * @param {number[]} indexes
 549   * @returns {{ u(new_indexes: number[]): void; p(...inputs: HTMLInputElement[]): void; r: () => void; }}
 550   */
 551  export function init_binding_group_dynamic(group, indexes) {
 552  	/**
 553  	 * @type {HTMLInputElement[]} */
 554  	let _group = get_binding_group(group);
 555  
 556  	/**
 557  	 * @type {HTMLInputElement[]} */
 558  	let _inputs;
 559  
 560  	function get_binding_group(group) {
 561  		for (let i = 0; i < indexes.length; i++) {
 562  			group = group[indexes[i]] = group[indexes[i]] || [];
 563  		}
 564  		return group;
 565  	}
 566  
 567  	/**
 568  	 * @returns {void} */
 569  	function push() {
 570  		_inputs.forEach((input) => _group.push(input));
 571  	}
 572  
 573  	/**
 574  	 * @returns {void} */
 575  	function remove() {
 576  		_inputs.forEach((input) => _group.splice(_group.indexOf(input), 1));
 577  	}
 578  	return {
 579  		/* update */ u(new_indexes) {
 580  			indexes = new_indexes;
 581  			const new_group = get_binding_group(group);
 582  			if (new_group !== _group) {
 583  				remove();
 584  				_group = new_group;
 585  				push();
 586  			}
 587  		},
 588  		/* push */ p(...inputs) {
 589  			_inputs = inputs;
 590  			push();
 591  		},
 592  		/* remove */ r: remove
 593  	};
 594  }
 595  
 596  /** @returns {number} */
 597  export function to_number(value) {
 598  	return value === '' ? null : +value;
 599  }
 600  
 601  /** @returns {any[]} */
 602  export function time_ranges_to_array(ranges) {
 603  	const array = [];
 604  	for (let i = 0; i < ranges.length; i += 1) {
 605  		array.push({ start: ranges.start(i), end: ranges.end(i) });
 606  	}
 607  	return array;
 608  }
 609  
 610  /**
 611   * @param {Element} element
 612   * @returns {ChildNode[]}
 613   */
 614  export function children(element) {
 615  	return Array.from(element.childNodes);
 616  }
 617  
 618  /**
 619   * @param {ChildNodeArray} nodes
 620   * @returns {void}
 621   */
 622  function init_claim_info(nodes) {
 623  	if (nodes.claim_info === undefined) {
 624  		nodes.claim_info = { last_index: 0, total_claimed: 0 };
 625  	}
 626  }
 627  
 628  /**
 629   * @template {ChildNodeEx} R
 630   * @param {ChildNodeArray} nodes
 631   * @param {(node: ChildNodeEx) => node is R} predicate
 632   * @param {(node: ChildNodeEx) => ChildNodeEx | undefined} process_node
 633   * @param {() => R} create_node
 634   * @param {boolean} dont_update_last_index
 635   * @returns {R}
 636   */
 637  function claim_node(nodes, predicate, process_node, create_node, dont_update_last_index = false) {
 638  	// Try to find nodes in an order such that we lengthen the longest increasing subsequence
 639  	init_claim_info(nodes);
 640  	const result_node = (() => {
 641  		// We first try to find an element after the previous one
 642  		for (let i = nodes.claim_info.last_index; i < nodes.length; i++) {
 643  			const node = nodes[i];
 644  			if (predicate(node)) {
 645  				const replacement = process_node(node);
 646  				if (replacement === undefined) {
 647  					nodes.splice(i, 1);
 648  				} else {
 649  					nodes[i] = replacement;
 650  				}
 651  				if (!dont_update_last_index) {
 652  					nodes.claim_info.last_index = i;
 653  				}
 654  				return node;
 655  			}
 656  		}
 657  		// Otherwise, we try to find one before
 658  		// We iterate in reverse so that we don't go too far back
 659  		for (let i = nodes.claim_info.last_index - 1; i >= 0; i--) {
 660  			const node = nodes[i];
 661  			if (predicate(node)) {
 662  				const replacement = process_node(node);
 663  				if (replacement === undefined) {
 664  					nodes.splice(i, 1);
 665  				} else {
 666  					nodes[i] = replacement;
 667  				}
 668  				if (!dont_update_last_index) {
 669  					nodes.claim_info.last_index = i;
 670  				} else if (replacement === undefined) {
 671  					// Since we spliced before the last_index, we decrease it
 672  					nodes.claim_info.last_index--;
 673  				}
 674  				return node;
 675  			}
 676  		}
 677  		// If we can't find any matching node, we create a new one
 678  		return create_node();
 679  	})();
 680  	result_node.claim_order = nodes.claim_info.total_claimed;
 681  	nodes.claim_info.total_claimed += 1;
 682  	return result_node;
 683  }
 684  
 685  /**
 686   * @param {ChildNodeArray} nodes
 687   * @param {string} name
 688   * @param {{ [key: string]: boolean }} attributes
 689   * @param {(name: string) => Element | SVGElement} create_element
 690   * @returns {Element | SVGElement}
 691   */
 692  function claim_element_base(nodes, name, attributes, create_element) {
 693  	return claim_node(
 694  		nodes,
 695  		/** @returns {node is Element | SVGElement} */
 696  		(node) => node.nodeName === name,
 697  		/** @param {Element} node */
 698  		(node) => {
 699  			const remove = [];
 700  			for (let j = 0; j < node.attributes.length; j++) {
 701  				const attribute = node.attributes[j];
 702  				if (!attributes[attribute.name]) {
 703  					remove.push(attribute.name);
 704  				}
 705  			}
 706  			remove.forEach((v) => node.removeAttribute(v));
 707  			return undefined;
 708  		},
 709  		() => create_element(name)
 710  	);
 711  }
 712  
 713  /**
 714   * @param {ChildNodeArray} nodes
 715   * @param {string} name
 716   * @param {{ [key: string]: boolean }} attributes
 717   * @returns {Element | SVGElement}
 718   */
 719  export function claim_element(nodes, name, attributes) {
 720  	return claim_element_base(nodes, name, attributes, element);
 721  }
 722  
 723  /**
 724   * @param {ChildNodeArray} nodes
 725   * @param {string} name
 726   * @param {{ [key: string]: boolean }} attributes
 727   * @returns {Element | SVGElement}
 728   */
 729  export function claim_svg_element(nodes, name, attributes) {
 730  	return claim_element_base(nodes, name, attributes, svg_element);
 731  }
 732  
 733  /**
 734   * @param {ChildNodeArray} nodes
 735   * @returns {Text}
 736   */
 737  export function claim_text(nodes, data) {
 738  	return claim_node(
 739  		nodes,
 740  		/** @returns {node is Text} */
 741  		(node) => node.nodeType === 3,
 742  		/** @param {Text} node */
 743  		(node) => {
 744  			const data_str = '' + data;
 745  			if (node.data.startsWith(data_str)) {
 746  				if (node.data.length !== data_str.length) {
 747  					return node.splitText(data_str.length);
 748  				}
 749  			} else {
 750  				node.data = data_str;
 751  			}
 752  		},
 753  		() => text(data),
 754  		true // Text nodes should not update last index since it is likely not worth it to eliminate an increasing subsequence of actual elements
 755  	);
 756  }
 757  
 758  /**
 759   * @returns {Text} */
 760  export function claim_space(nodes) {
 761  	return claim_text(nodes, ' ');
 762  }
 763  
 764  /**
 765   * @param {ChildNodeArray} nodes
 766   * @returns {Comment}
 767   */
 768  export function claim_comment(nodes, data) {
 769  	return claim_node(
 770  		nodes,
 771  		/** @returns {node is Comment} */
 772  		(node) => node.nodeType === 8,
 773  		/** @param {Comment} node */
 774  		(node) => {
 775  			node.data = '' + data;
 776  			return undefined;
 777  		},
 778  		() => comment(data),
 779  		true
 780  	);
 781  }
 782  
 783  function get_comment_idx(nodes, text, start) {
 784  	for (let i = start; i < nodes.length; i += 1) {
 785  		const node = nodes[i];
 786  		if (node.nodeType === 8 /* comment node */ && node.textContent.trim() === text) {
 787  			return i;
 788  		}
 789  	}
 790  	return -1;
 791  }
 792  
 793  /**
 794   * @param {boolean} is_svg
 795   * @returns {HtmlTagHydration}
 796   */
 797  export function claim_html_tag(nodes, is_svg) {
 798  	// find html opening tag
 799  	const start_index = get_comment_idx(nodes, 'HTML_TAG_START', 0);
 800  	const end_index = get_comment_idx(nodes, 'HTML_TAG_END', start_index + 1);
 801  	if (start_index === -1 || end_index === -1) {
 802  		return new HtmlTagHydration(is_svg);
 803  	}
 804  
 805  	init_claim_info(nodes);
 806  	const html_tag_nodes = nodes.splice(start_index, end_index - start_index + 1);
 807  	detach(html_tag_nodes[0]);
 808  	detach(html_tag_nodes[html_tag_nodes.length - 1]);
 809  	const claimed_nodes = html_tag_nodes.slice(1, html_tag_nodes.length - 1);
 810  	if (claimed_nodes.length === 0) {
 811  		return new HtmlTagHydration(is_svg);
 812  	}
 813  	for (const n of claimed_nodes) {
 814  		n.claim_order = nodes.claim_info.total_claimed;
 815  		nodes.claim_info.total_claimed += 1;
 816  	}
 817  	return new HtmlTagHydration(is_svg, claimed_nodes);
 818  }
 819  
 820  /**
 821   * @param {Text} text
 822   * @param {unknown} data
 823   * @returns {void}
 824   */
 825  export function set_data(text, data) {
 826  	data = '' + data;
 827  	if (text.data === data) return;
 828  	text.data = /** @type {string} */ (data);
 829  }
 830  
 831  /**
 832   * @param {Text} text
 833   * @param {unknown} data
 834   * @returns {void}
 835   */
 836  export function set_data_contenteditable(text, data) {
 837  	data = '' + data;
 838  	if (text.wholeText === data) return;
 839  	text.data = /** @type {string} */ (data);
 840  }
 841  
 842  /**
 843   * @param {Text} text
 844   * @param {unknown} data
 845   * @param {string} attr_value
 846   * @returns {void}
 847   */
 848  export function set_data_maybe_contenteditable(text, data, attr_value) {
 849  	if (~contenteditable_truthy_values.indexOf(attr_value)) {
 850  		set_data_contenteditable(text, data);
 851  	} else {
 852  		set_data(text, data);
 853  	}
 854  }
 855  
 856  /**
 857   * @returns {void} */
 858  export function set_input_value(input, value) {
 859  	input.value = value == null ? '' : value;
 860  }
 861  
 862  /**
 863   * @returns {void} */
 864  export function set_input_type(input, type) {
 865  	try {
 866  		input.type = type;
 867  	} catch (e) {
 868  		// do nothing
 869  	}
 870  }
 871  
 872  /**
 873   * @returns {void} */
 874  export function set_style(node, key, value, important) {
 875  	if (value == null) {
 876  		node.style.removeProperty(key);
 877  	} else {
 878  		node.style.setProperty(key, value, important ? 'important' : '');
 879  	}
 880  }
 881  
 882  /**
 883   * @returns {void} */
 884  export function select_option(select, value, mounting) {
 885  	for (let i = 0; i < select.options.length; i += 1) {
 886  		const option = select.options[i];
 887  		if (option.__value === value) {
 888  			option.selected = true;
 889  			return;
 890  		}
 891  	}
 892  	if (!mounting || value !== undefined) {
 893  		select.selectedIndex = -1; // no option should be selected
 894  	}
 895  }
 896  
 897  /**
 898   * @returns {void} */
 899  export function select_options(select, value) {
 900  	for (let i = 0; i < select.options.length; i += 1) {
 901  		const option = select.options[i];
 902  		option.selected = ~value.indexOf(option.__value);
 903  	}
 904  }
 905  
 906  export function select_value(select) {
 907  	const selected_option = select.querySelector(':checked');
 908  	return selected_option && selected_option.__value;
 909  }
 910  
 911  export function select_multiple_value(select) {
 912  	return [].map.call(select.querySelectorAll(':checked'), (option) => option.__value);
 913  }
 914  // unfortunately this can't be a constant as that wouldn't be tree-shakeable
 915  // so we cache the result instead
 916  
 917  /**
 918   * @type {boolean} */
 919  let crossorigin;
 920  
 921  /**
 922   * @returns {boolean} */
 923  export function is_crossorigin() {
 924  	if (crossorigin === undefined) {
 925  		crossorigin = false;
 926  		try {
 927  			if (typeof window !== 'undefined' && window.parent) {
 928  				void window.parent.document;
 929  			}
 930  		} catch (error) {
 931  			crossorigin = true;
 932  		}
 933  	}
 934  	return crossorigin;
 935  }
 936  
 937  /**
 938   * @param {HTMLElement} node
 939   * @param {() => void} fn
 940   * @returns {() => void}
 941   */
 942  export function add_iframe_resize_listener(node, fn) {
 943  	const computed_style = getComputedStyle(node);
 944  	if (computed_style.position === 'static') {
 945  		node.style.position = 'relative';
 946  	}
 947  	const iframe = element('iframe');
 948  	iframe.setAttribute(
 949  		'style',
 950  		'display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; ' +
 951  			'overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: -1;'
 952  	);
 953  	iframe.setAttribute('aria-hidden', 'true');
 954  	iframe.tabIndex = -1;
 955  	const crossorigin = is_crossorigin();
 956  
 957  	/**
 958  	 * @type {() => void}
 959  	 */
 960  	let unsubscribe;
 961  	if (crossorigin) {
 962  		iframe.src = "data:text/html,<script>onresize=function(){parent.postMessage(0,'*')}</script>";
 963  		unsubscribe = listen(
 964  			window,
 965  			'message',
 966  			/** @param {MessageEvent} event */ (event) => {
 967  				if (event.source === iframe.contentWindow) fn();
 968  			}
 969  		);
 970  	} else {
 971  		iframe.src = 'about:blank';
 972  		iframe.onload = () => {
 973  			unsubscribe = listen(iframe.contentWindow, 'resize', fn);
 974  			// make sure an initial resize event is fired _after_ the iframe is loaded (which is asynchronous)
 975  			// see https://github.com/sveltejs/svelte/issues/4233
 976  			fn();
 977  		};
 978  	}
 979  	append(node, iframe);
 980  	return () => {
 981  		if (crossorigin) {
 982  			unsubscribe();
 983  		} else if (unsubscribe && iframe.contentWindow) {
 984  			unsubscribe();
 985  		}
 986  		detach(iframe);
 987  	};
 988  }
 989  export const resize_observer_content_box = /* @__PURE__ */ new ResizeObserverSingleton({
 990  	box: 'content-box'
 991  });
 992  export const resize_observer_border_box = /* @__PURE__ */ new ResizeObserverSingleton({
 993  	box: 'border-box'
 994  });
 995  export const resize_observer_device_pixel_content_box = /* @__PURE__ */ new ResizeObserverSingleton(
 996  	{ box: 'device-pixel-content-box' }
 997  );
 998  export { ResizeObserverSingleton };
 999  
1000  /**
1001   * @returns {void} */
1002  export function toggle_class(element, name, toggle) {
1003  	// The `!!` is required because an `undefined` flag means flipping the current state.
1004  	element.classList.toggle(name, !!toggle);
1005  }
1006  
1007  /**
1008   * @template T
1009   * @param {string} type
1010   * @param {T} [detail]
1011   * @param {{ bubbles?: boolean, cancelable?: boolean }} [options]
1012   * @returns {CustomEvent<T>}
1013   */
1014  export function custom_event(type, detail, { bubbles = false, cancelable = false } = {}) {
1015  	return new CustomEvent(type, { detail, bubbles, cancelable });
1016  }
1017  
1018  /**
1019   * @param {string} selector
1020   * @param {HTMLElement} parent
1021   * @returns {ChildNodeArray}
1022   */
1023  export function query_selector_all(selector, parent = document.body) {
1024  	return Array.from(parent.querySelectorAll(selector));
1025  }
1026  
1027  /**
1028   * @param {string} nodeId
1029   * @param {HTMLElement} head
1030   * @returns {any[]}
1031   */
1032  export function head_selector(nodeId, head) {
1033  	const result = [];
1034  	let started = 0;
1035  	for (const node of head.childNodes) {
1036  		if (node.nodeType === 8 /* comment node */) {
1037  			const comment = node.textContent.trim();
1038  			if (comment === `HEAD_${nodeId}_END`) {
1039  				started -= 1;
1040  				result.push(node);
1041  			} else if (comment === `HEAD_${nodeId}_START`) {
1042  				started += 1;
1043  				result.push(node);
1044  			}
1045  		} else if (started > 0) {
1046  			result.push(node);
1047  		}
1048  	}
1049  	return result;
1050  }
1051  /** */
1052  export class HtmlTag {
1053  	/**
1054  	 * @private
1055  	 * @default false
1056  	 */
1057  	is_svg = false;
1058  	/** parent for creating node */
1059  	e = undefined;
1060  	/** html tag nodes */
1061  	n = undefined;
1062  	/** target */
1063  	t = undefined;
1064  	/** anchor */
1065  	a = undefined;
1066  	constructor(is_svg = false) {
1067  		this.is_svg = is_svg;
1068  		this.e = this.n = null;
1069  	}
1070  
1071  	/**
1072  	 * @param {string} html
1073  	 * @returns {void}
1074  	 */
1075  	c(html) {
1076  		this.h(html);
1077  	}
1078  
1079  	/**
1080  	 * @param {string} html
1081  	 * @param {HTMLElement | SVGElement} target
1082  	 * @param {HTMLElement | SVGElement} anchor
1083  	 * @returns {void}
1084  	 */
1085  	m(html, target, anchor = null) {
1086  		if (!this.e) {
1087  			if (this.is_svg)
1088  				this.e = svg_element(/** @type {keyof SVGElementTagNameMap} */ (target.nodeName));
1089  			/** #7364  target for <template> may be provided as #document-fragment(11) */ else
1090  				this.e = element(
1091  					/** @type {keyof HTMLElementTagNameMap} */ (
1092  						target.nodeType === 11 ? 'TEMPLATE' : target.nodeName
1093  					)
1094  				);
1095  			this.t =
1096  				target.tagName !== 'TEMPLATE'
1097  					? target
1098  					: /** @type {HTMLTemplateElement} */ (target).content;
1099  			this.c(html);
1100  		}
1101  		this.i(anchor);
1102  	}
1103  
1104  	/**
1105  	 * @param {string} html
1106  	 * @returns {void}
1107  	 */
1108  	h(html) {
1109  		this.e.innerHTML = html;
1110  		this.n = Array.from(
1111  			this.e.nodeName === 'TEMPLATE' ? this.e.content.childNodes : this.e.childNodes
1112  		);
1113  	}
1114  
1115  	/**
1116  	 * @returns {void} */
1117  	i(anchor) {
1118  		for (let i = 0; i < this.n.length; i += 1) {
1119  			insert(this.t, this.n[i], anchor);
1120  		}
1121  	}
1122  
1123  	/**
1124  	 * @param {string} html
1125  	 * @returns {void}
1126  	 */
1127  	p(html) {
1128  		this.d();
1129  		this.h(html);
1130  		this.i(this.a);
1131  	}
1132  
1133  	/**
1134  	 * @returns {void} */
1135  	d() {
1136  		this.n.forEach(detach);
1137  	}
1138  }
1139  
1140  export class HtmlTagHydration extends HtmlTag {
1141  	/** @type {Element[]} hydration claimed nodes */
1142  	l = undefined;
1143  
1144  	constructor(is_svg = false, claimed_nodes) {
1145  		super(is_svg);
1146  		this.e = this.n = null;
1147  		this.l = claimed_nodes;
1148  	}
1149  
1150  	/**
1151  	 * @param {string} html
1152  	 * @returns {void}
1153  	 */
1154  	c(html) {
1155  		if (this.l) {
1156  			this.n = this.l;
1157  		} else {
1158  			super.c(html);
1159  		}
1160  	}
1161  
1162  	/**
1163  	 * @returns {void} */
1164  	i(anchor) {
1165  		for (let i = 0; i < this.n.length; i += 1) {
1166  			insert_hydration(this.t, this.n[i], anchor);
1167  		}
1168  	}
1169  }
1170  
1171  /**
1172   * @param {NamedNodeMap} attributes
1173   * @returns {{}}
1174   */
1175  export function attribute_to_object(attributes) {
1176  	const result = {};
1177  	for (const attribute of attributes) {
1178  		result[attribute.name] = attribute.value;
1179  	}
1180  	return result;
1181  }
1182  
1183  const escaped = {
1184  	'"': '&quot;',
1185  	'&': '&amp;',
1186  	'<': '&lt;'
1187  };
1188  
1189  const regex_attribute_characters_to_escape = /["&<]/g;
1190  
1191  /**
1192   * Note that the attribute itself should be surrounded in double quotes
1193   * @param {any} attribute
1194   */
1195  function escape_attribute(attribute) {
1196  	return String(attribute).replace(regex_attribute_characters_to_escape, (match) => escaped[match]);
1197  }
1198  
1199  /**
1200   * @param {Record<string, string>} attributes
1201   */
1202  export function stringify_spread(attributes) {
1203  	let str = ' ';
1204  	for (const key in attributes) {
1205  		if (attributes[key] != null) {
1206  			str += `${key}="${escape_attribute(attributes[key])}" `;
1207  		}
1208  	}
1209  
1210  	return str;
1211  }
1212  
1213  /**
1214   * @param {HTMLElement} element
1215   * @returns {{}}
1216   */
1217  export function get_custom_elements_slots(element) {
1218  	const result = {};
1219  	element.childNodes.forEach(
1220  		/** @param {Element} node */ (node) => {
1221  			result[node.slot || 'default'] = true;
1222  		}
1223  	);
1224  	return result;
1225  }
1226  
1227  export function construct_svelte_component(component, props) {
1228  	return new component(props);
1229  }
1230  
1231  /**
1232   * @typedef {Node & {
1233   * 	claim_order?: number;
1234   * 	hydrate_init?: true;
1235   * 	actual_end_child?: NodeEx;
1236   * 	childNodes: NodeListOf<NodeEx>;
1237   * }} NodeEx
1238   */
1239  
1240  /** @typedef {ChildNode & NodeEx} ChildNodeEx */
1241  
1242  /** @typedef {NodeEx & { claim_order: number }} NodeEx2 */
1243  
1244  /**
1245   * @typedef {ChildNodeEx[] & {
1246   * 	claim_info?: {
1247   * 		last_index: number;
1248   * 		total_claimed: number;
1249   * 	};
1250   * }} ChildNodeArray
1251   */