|
|
|
|
@ -23,6 +23,19 @@ import { ATTACHMENT_KEY, NAMESPACE_HTML, UNINITIALIZED } from '../../../../const
|
|
|
|
|
import { branch, destroy_effect, effect, managed } from '../../reactivity/effects.js';
|
|
|
|
|
import { init_select, select_option } from './bindings/select.js';
|
|
|
|
|
import { flatten } from '../../reactivity/async.js';
|
|
|
|
|
import {
|
|
|
|
|
has_attribute,
|
|
|
|
|
get_attribute,
|
|
|
|
|
remove_attribute,
|
|
|
|
|
set_attribute as set_attribute_op,
|
|
|
|
|
remove_event_listener,
|
|
|
|
|
node_name,
|
|
|
|
|
set_element_value,
|
|
|
|
|
set_element_checked,
|
|
|
|
|
set_element_default_value,
|
|
|
|
|
set_element_default_checked
|
|
|
|
|
} from '../operations.js';
|
|
|
|
|
import { renderer } from '../../custom-renderer/state.js';
|
|
|
|
|
|
|
|
|
|
export const CLASS = Symbol('class');
|
|
|
|
|
export const STYLE = Symbol('style');
|
|
|
|
|
@ -45,6 +58,8 @@ const PROGRESS_TAG = IS_XHTML ? 'progress' : 'PROGRESS';
|
|
|
|
|
export function remove_input_defaults(input) {
|
|
|
|
|
if (!hydrating) return;
|
|
|
|
|
|
|
|
|
|
// we should be safe here from custom renderers as this only run during hydration
|
|
|
|
|
|
|
|
|
|
var already_removed = false;
|
|
|
|
|
|
|
|
|
|
// We try and remove the default attributes later, rather than sync during hydration.
|
|
|
|
|
@ -56,13 +71,13 @@ export function remove_input_defaults(input) {
|
|
|
|
|
already_removed = true;
|
|
|
|
|
|
|
|
|
|
// Remove the attributes but preserve the values
|
|
|
|
|
if (input.hasAttribute('value')) {
|
|
|
|
|
if (has_attribute(input, 'value')) {
|
|
|
|
|
var value = input.value;
|
|
|
|
|
set_attribute(input, 'value', null);
|
|
|
|
|
input.value = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (input.hasAttribute('checked')) {
|
|
|
|
|
if (has_attribute(input, 'checked')) {
|
|
|
|
|
var checked = input.checked;
|
|
|
|
|
set_attribute(input, 'checked', null);
|
|
|
|
|
input.checked = checked;
|
|
|
|
|
@ -89,13 +104,12 @@ export function set_value(element, value) {
|
|
|
|
|
value ?? undefined) ||
|
|
|
|
|
// @ts-expect-error
|
|
|
|
|
// `progress` elements always need their value set when it's `0`
|
|
|
|
|
(element.value === value && (value !== 0 || element.nodeName !== PROGRESS_TAG))
|
|
|
|
|
(element.value === value && (value !== 0 || node_name(element) !== PROGRESS_TAG))
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @ts-expect-error
|
|
|
|
|
element.value = value ?? '';
|
|
|
|
|
set_element_value(element, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -114,8 +128,7 @@ export function set_checked(element, checked) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @ts-expect-error
|
|
|
|
|
element.checked = checked;
|
|
|
|
|
set_element_checked(element, checked);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -129,11 +142,11 @@ export function set_selected(element, selected) {
|
|
|
|
|
if (selected) {
|
|
|
|
|
// The selected option could've changed via user selection, and
|
|
|
|
|
// setting the value without this check would set it back.
|
|
|
|
|
if (!element.hasAttribute('selected')) {
|
|
|
|
|
element.setAttribute('selected', '');
|
|
|
|
|
if (!has_attribute(element, 'selected')) {
|
|
|
|
|
set_attribute_op(element, 'selected', '');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
element.removeAttribute('selected');
|
|
|
|
|
remove_attribute(element, 'selected');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -143,9 +156,7 @@ export function set_selected(element, selected) {
|
|
|
|
|
* @param {boolean} checked
|
|
|
|
|
*/
|
|
|
|
|
export function set_default_checked(element, checked) {
|
|
|
|
|
const existing_value = element.checked;
|
|
|
|
|
element.defaultChecked = checked;
|
|
|
|
|
element.checked = existing_value;
|
|
|
|
|
set_element_default_checked(element, checked);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -154,9 +165,7 @@ export function set_default_checked(element, checked) {
|
|
|
|
|
* @param {string} value
|
|
|
|
|
*/
|
|
|
|
|
export function set_default_value(element, value) {
|
|
|
|
|
const existing_value = element.value;
|
|
|
|
|
element.defaultValue = value;
|
|
|
|
|
element.value = existing_value;
|
|
|
|
|
set_element_default_value(element, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -169,12 +178,12 @@ export function set_attribute(element, attribute, value, skip_warning) {
|
|
|
|
|
var attributes = get_attributes(element);
|
|
|
|
|
|
|
|
|
|
if (hydrating) {
|
|
|
|
|
attributes[attribute] = element.getAttribute(attribute);
|
|
|
|
|
attributes[attribute] = get_attribute(element, attribute);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
attribute === 'src' ||
|
|
|
|
|
attribute === 'srcset' ||
|
|
|
|
|
(attribute === 'href' && element.nodeName === LINK_TAG)
|
|
|
|
|
(attribute === 'href' && node_name(element) === LINK_TAG)
|
|
|
|
|
) {
|
|
|
|
|
if (!skip_warning) {
|
|
|
|
|
check_src_in_dev_hydration(element, attribute, value ?? '');
|
|
|
|
|
@ -196,12 +205,12 @@ export function set_attribute(element, attribute, value, skip_warning) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value == null) {
|
|
|
|
|
element.removeAttribute(attribute);
|
|
|
|
|
remove_attribute(element, attribute);
|
|
|
|
|
} else if (typeof value !== 'string' && get_setters(element).includes(attribute)) {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
element[attribute] = value;
|
|
|
|
|
} else {
|
|
|
|
|
element.setAttribute(attribute, value);
|
|
|
|
|
set_attribute_op(element, attribute, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -211,6 +220,7 @@ export function set_attribute(element, attribute, value, skip_warning) {
|
|
|
|
|
* @param {string} value
|
|
|
|
|
*/
|
|
|
|
|
export function set_xlink_attribute(dom, attribute, value) {
|
|
|
|
|
// custom renderer safe since we never emit this
|
|
|
|
|
dom.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -220,6 +230,7 @@ export function set_xlink_attribute(dom, attribute, value) {
|
|
|
|
|
* @param {any} value
|
|
|
|
|
*/
|
|
|
|
|
export function set_custom_element_data(node, prop, value) {
|
|
|
|
|
// custom renderer safe since we never emit this
|
|
|
|
|
// We need to ensure that setting custom element props, which can
|
|
|
|
|
// invoke lifecycle methods on other custom elements, does not also
|
|
|
|
|
// associate those lifecycle methods with the current active reaction
|
|
|
|
|
@ -244,10 +255,10 @@ export function set_custom_element_data(node, prop, value) {
|
|
|
|
|
// Don't compute setters for custom elements while they aren't registered yet,
|
|
|
|
|
// because during their upgrade/instantiation they might add more setters.
|
|
|
|
|
// Instead, fall back to a simple "an object, then set as property" heuristic.
|
|
|
|
|
(setters_cache.has(node.getAttribute('is') || node.nodeName) ||
|
|
|
|
|
(setters_cache.has(get_attribute(node, 'is') || (node_name(node) ?? '')) ||
|
|
|
|
|
// customElements may not be available in browser extension contexts
|
|
|
|
|
!customElements ||
|
|
|
|
|
customElements.get(node.getAttribute('is') || node.nodeName.toLowerCase())
|
|
|
|
|
customElements.get(get_attribute(node, 'is') || (node_name(node) ?? '').toLowerCase())
|
|
|
|
|
? get_setters(node).includes(prop)
|
|
|
|
|
: value && typeof value === 'object')
|
|
|
|
|
) {
|
|
|
|
|
@ -286,7 +297,7 @@ function set_attributes(
|
|
|
|
|
should_remove_defaults = false,
|
|
|
|
|
skip_warning = false
|
|
|
|
|
) {
|
|
|
|
|
if (hydrating && should_remove_defaults && element.nodeName === INPUT_TAG) {
|
|
|
|
|
if (hydrating && should_remove_defaults && node_name(element) === INPUT_TAG) {
|
|
|
|
|
var input = /** @type {HTMLInputElement} */ (element);
|
|
|
|
|
var attribute = input.type === 'checkbox' ? 'defaultChecked' : 'defaultValue';
|
|
|
|
|
|
|
|
|
|
@ -308,7 +319,7 @@ function set_attributes(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var current = prev || {};
|
|
|
|
|
var is_option_element = element.nodeName === OPTION_TAG;
|
|
|
|
|
var is_option_element = node_name(element) === OPTION_TAG;
|
|
|
|
|
|
|
|
|
|
for (var key in prev) {
|
|
|
|
|
if (!(key in next)) {
|
|
|
|
|
@ -370,7 +381,7 @@ function set_attributes(
|
|
|
|
|
var prev_value = current[key];
|
|
|
|
|
|
|
|
|
|
// Skip if value is unchanged, unless it's `undefined` and the element still has the attribute
|
|
|
|
|
if (value === prev_value && !(value === undefined && element.hasAttribute(key))) {
|
|
|
|
|
if (value === prev_value && !(value === undefined && has_attribute(element, key))) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -398,7 +409,7 @@ function set_attributes(
|
|
|
|
|
// https://github.com/sveltejs/svelte/issues/11903
|
|
|
|
|
if (value != null) continue;
|
|
|
|
|
|
|
|
|
|
element.removeEventListener(event_name, current[event_handle_key], opts);
|
|
|
|
|
remove_event_listener(element, event_name, current[event_handle_key], opts);
|
|
|
|
|
current[event_handle_key] = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -424,7 +435,8 @@ function set_attributes(
|
|
|
|
|
} else if (!is_custom_element && (key === '__value' || (key === 'value' && value != null))) {
|
|
|
|
|
// @ts-ignore We're not running this for custom elements because __value is actually
|
|
|
|
|
// how Lit stores the current value on the element, and messing with that would break things.
|
|
|
|
|
element.value = element.__value = value;
|
|
|
|
|
element.__value = value;
|
|
|
|
|
set_element_value(element, value);
|
|
|
|
|
} else if (key === 'selected' && is_option_element) {
|
|
|
|
|
set_selected(/** @type {HTMLOptionElement} */ (element), value);
|
|
|
|
|
} else {
|
|
|
|
|
@ -437,25 +449,28 @@ function set_attributes(
|
|
|
|
|
|
|
|
|
|
if (value == null && !is_custom_element && !is_default) {
|
|
|
|
|
attributes[key] = null;
|
|
|
|
|
|
|
|
|
|
if (name === 'value' || name === 'checked') {
|
|
|
|
|
if ((name === 'value' || name === 'checked') && renderer == null) {
|
|
|
|
|
// removing value/checked also removes defaultValue/defaultChecked — preserve
|
|
|
|
|
let input = /** @type {HTMLInputElement} */ (element);
|
|
|
|
|
const use_default = prev === undefined;
|
|
|
|
|
if (name === 'value') {
|
|
|
|
|
let previous = input.defaultValue;
|
|
|
|
|
input.removeAttribute(name);
|
|
|
|
|
input.defaultValue = previous;
|
|
|
|
|
remove_attribute(input, name);
|
|
|
|
|
set_element_default_value(input, previous);
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
input.value = input.__value = use_default ? previous : null;
|
|
|
|
|
set_element_value(input, (input.__value = use_default ? previous : null));
|
|
|
|
|
} else {
|
|
|
|
|
let previous = input.defaultChecked;
|
|
|
|
|
input.removeAttribute(name);
|
|
|
|
|
input.defaultChecked = previous;
|
|
|
|
|
input.checked = use_default ? previous : false;
|
|
|
|
|
remove_attribute(input, name);
|
|
|
|
|
set_element_default_checked(input, previous);
|
|
|
|
|
set_element_checked(input, use_default ? previous : false);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
element.removeAttribute(key);
|
|
|
|
|
remove_attribute(element, key);
|
|
|
|
|
if (name === 'value') {
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
element.__value = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (
|
|
|
|
|
is_default ||
|
|
|
|
|
@ -505,7 +520,7 @@ export function attribute_effect(
|
|
|
|
|
/** @type {Record<symbol, Effect>} */
|
|
|
|
|
var effects = {};
|
|
|
|
|
|
|
|
|
|
var is_select = element.nodeName === SELECT_TAG;
|
|
|
|
|
var is_select = node_name(element) === SELECT_TAG;
|
|
|
|
|
var inited = false;
|
|
|
|
|
|
|
|
|
|
managed(() => {
|
|
|
|
|
@ -563,7 +578,7 @@ function get_attributes(element) {
|
|
|
|
|
return /** @type {Record<string | symbol, unknown>} **/ (
|
|
|
|
|
// @ts-expect-error
|
|
|
|
|
element.__attributes ??= {
|
|
|
|
|
[IS_CUSTOM_ELEMENT]: element.nodeName.includes('-'),
|
|
|
|
|
[IS_CUSTOM_ELEMENT]: (node_name(element) ?? '').includes('-'),
|
|
|
|
|
[IS_HTML]: element.namespaceURI === NAMESPACE_HTML
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
@ -574,7 +589,9 @@ var setters_cache = new Map();
|
|
|
|
|
|
|
|
|
|
/** @param {Element} element */
|
|
|
|
|
function get_setters(element) {
|
|
|
|
|
var cache_key = element.getAttribute('is') || element.nodeName;
|
|
|
|
|
// if we have a custom renderer we just skip the check all together
|
|
|
|
|
if (renderer) return [];
|
|
|
|
|
var cache_key = get_attribute(element, 'is') || (node_name(element) ?? '');
|
|
|
|
|
var setters = setters_cache.get(cache_key);
|
|
|
|
|
if (setters) return setters;
|
|
|
|
|
setters_cache.set(cache_key, (setters = []));
|
|
|
|
|
@ -606,12 +623,13 @@ function get_setters(element) {
|
|
|
|
|
* @param {string} value
|
|
|
|
|
*/
|
|
|
|
|
function check_src_in_dev_hydration(element, attribute, value) {
|
|
|
|
|
if (!DEV) return;
|
|
|
|
|
if (!DEV || renderer != null) return;
|
|
|
|
|
if (attribute === 'srcset' && srcset_url_equal(element, value)) return;
|
|
|
|
|
if (src_url_equal(element.getAttribute(attribute) ?? '', value)) return;
|
|
|
|
|
if (src_url_equal(get_attribute(element, attribute) ?? '', value)) return;
|
|
|
|
|
|
|
|
|
|
w.hydration_attribute_changed(
|
|
|
|
|
attribute,
|
|
|
|
|
// TODO RENDERER figure out what to do here
|
|
|
|
|
element.outerHTML.replace(element.innerHTML, element.innerHTML && '...'),
|
|
|
|
|
String(value)
|
|
|
|
|
);
|
|
|
|
|
@ -623,7 +641,7 @@ function check_src_in_dev_hydration(element, attribute, value) {
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
function src_url_equal(element_src, url) {
|
|
|
|
|
if (element_src === url) return true;
|
|
|
|
|
if (element_src === url || renderer != null) return true;
|
|
|
|
|
return new URL(element_src, document.baseURI).href === new URL(url, document.baseURI).href;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -638,6 +656,7 @@ function split_srcset(srcset) {
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
function srcset_url_equal(element, srcset) {
|
|
|
|
|
if (renderer != null) return true;
|
|
|
|
|
var element_urls = split_srcset(element.srcset);
|
|
|
|
|
var urls = split_srcset(srcset);
|
|
|
|
|
|
|
|
|
|
|