mirror of https://github.com/sveltejs/svelte
commit
dd9bb711d2
@ -1,120 +1,49 @@
|
||||
import { to_class } from '../../../shared/attributes.js';
|
||||
import { hydrating } from '../hydration.js';
|
||||
|
||||
/**
|
||||
* @param {SVGElement} dom
|
||||
* @param {string} value
|
||||
* @param {string} [hash]
|
||||
* @returns {void}
|
||||
*/
|
||||
export function set_svg_class(dom, value, hash) {
|
||||
// @ts-expect-error need to add __className to patched prototype
|
||||
var prev_class_name = dom.__className;
|
||||
var next_class_name = to_class(value, hash);
|
||||
|
||||
if (hydrating && dom.getAttribute('class') === next_class_name) {
|
||||
// In case of hydration don't reset the class as it's already correct.
|
||||
// @ts-expect-error need to add __className to patched prototype
|
||||
dom.__className = next_class_name;
|
||||
} else if (
|
||||
prev_class_name !== next_class_name ||
|
||||
(hydrating && dom.getAttribute('class') !== next_class_name)
|
||||
) {
|
||||
if (next_class_name === '') {
|
||||
dom.removeAttribute('class');
|
||||
} else {
|
||||
dom.setAttribute('class', next_class_name);
|
||||
}
|
||||
|
||||
// @ts-expect-error need to add __className to patched prototype
|
||||
dom.__className = next_class_name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {MathMLElement} dom
|
||||
* @param {string} value
|
||||
* @param {Element} dom
|
||||
* @param {boolean | number} is_html
|
||||
* @param {string | null} value
|
||||
* @param {string} [hash]
|
||||
* @returns {void}
|
||||
* @param {Record<string, boolean>} [prev_classes]
|
||||
* @param {Record<string, boolean>} [next_classes]
|
||||
* @returns {Record<string, boolean> | undefined}
|
||||
*/
|
||||
export function set_mathml_class(dom, value, hash) {
|
||||
export function set_class(dom, is_html, value, hash, prev_classes, next_classes) {
|
||||
// @ts-expect-error need to add __className to patched prototype
|
||||
var prev_class_name = dom.__className;
|
||||
var next_class_name = to_class(value, hash);
|
||||
|
||||
if (hydrating && dom.getAttribute('class') === next_class_name) {
|
||||
// In case of hydration don't reset the class as it's already correct.
|
||||
// @ts-expect-error need to add __className to patched prototype
|
||||
dom.__className = next_class_name;
|
||||
} else if (
|
||||
prev_class_name !== next_class_name ||
|
||||
(hydrating && dom.getAttribute('class') !== next_class_name)
|
||||
) {
|
||||
if (next_class_name === '') {
|
||||
dom.removeAttribute('class');
|
||||
} else {
|
||||
dom.setAttribute('class', next_class_name);
|
||||
var prev = dom.__className;
|
||||
|
||||
if (hydrating || prev !== value) {
|
||||
var next_class_name = to_class(value, hash, next_classes);
|
||||
|
||||
if (!hydrating || next_class_name !== dom.getAttribute('class')) {
|
||||
// Removing the attribute when the value is only an empty string causes
|
||||
// performance issues vs simply making the className an empty string. So
|
||||
// we should only remove the class if the the value is nullish
|
||||
// and there no hash/directives :
|
||||
if (next_class_name == null) {
|
||||
dom.removeAttribute('class');
|
||||
} else if (is_html) {
|
||||
dom.className = next_class_name;
|
||||
} else {
|
||||
dom.setAttribute('class', next_class_name);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error need to add __className to patched prototype
|
||||
dom.__className = next_class_name;
|
||||
}
|
||||
}
|
||||
dom.__className = value;
|
||||
} else if (next_classes) {
|
||||
prev_classes ??= {};
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} dom
|
||||
* @param {string} value
|
||||
* @param {string} [hash]
|
||||
* @returns {void}
|
||||
*/
|
||||
export function set_class(dom, value, hash) {
|
||||
// @ts-expect-error need to add __className to patched prototype
|
||||
var prev_class_name = dom.__className;
|
||||
var next_class_name = to_class(value, hash);
|
||||
for (var key in next_classes) {
|
||||
var is_present = !!next_classes[key];
|
||||
|
||||
if (hydrating && dom.className === next_class_name) {
|
||||
// In case of hydration don't reset the class as it's already correct.
|
||||
// @ts-expect-error need to add __className to patched prototype
|
||||
dom.__className = next_class_name;
|
||||
} else if (
|
||||
prev_class_name !== next_class_name ||
|
||||
(hydrating && dom.className !== next_class_name)
|
||||
) {
|
||||
// Removing the attribute when the value is only an empty string causes
|
||||
// peformance issues vs simply making the className an empty string. So
|
||||
// we should only remove the class if the the value is nullish.
|
||||
if (value == null && !hash) {
|
||||
dom.removeAttribute('class');
|
||||
} else {
|
||||
dom.className = next_class_name;
|
||||
if (is_present !== !!prev_classes[key]) {
|
||||
dom.classList.toggle(key, is_present);
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error need to add __className to patched prototype
|
||||
dom.__className = next_class_name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template V
|
||||
* @param {V} value
|
||||
* @param {string} [hash]
|
||||
* @returns {string | V}
|
||||
*/
|
||||
function to_class(value, hash) {
|
||||
return (value == null ? '' : value) + (hash ? ' ' + hash : '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Element} dom
|
||||
* @param {string} class_name
|
||||
* @param {boolean} value
|
||||
* @returns {void}
|
||||
*/
|
||||
export function toggle_class(dom, class_name, value) {
|
||||
if (value) {
|
||||
if (dom.classList.contains(class_name)) return;
|
||||
dom.classList.add(class_name);
|
||||
} else {
|
||||
if (!dom.classList.contains(class_name)) return;
|
||||
dom.classList.remove(class_name);
|
||||
}
|
||||
return next_classes;
|
||||
}
|
||||
|
||||
@ -1,79 +1,85 @@
|
||||
import { run_all } from '../../shared/utils.js';
|
||||
|
||||
// Fallback for when requestIdleCallback is not available
|
||||
export const request_idle_callback =
|
||||
const request_idle_callback =
|
||||
typeof requestIdleCallback === 'undefined'
|
||||
? (/** @type {() => void} */ cb) => setTimeout(cb, 1)
|
||||
: requestIdleCallback;
|
||||
|
||||
let is_micro_task_queued = false;
|
||||
let is_idle_task_queued = false;
|
||||
|
||||
/** @type {Array<() => void>} */
|
||||
let queued_boundary_microtasks = [];
|
||||
let boundary_micro_tasks = [];
|
||||
|
||||
/** @type {Array<() => void>} */
|
||||
let queued_post_microtasks = [];
|
||||
let micro_tasks = [];
|
||||
|
||||
/** @type {Array<() => void>} */
|
||||
let queued_idle_tasks = [];
|
||||
let idle_tasks = [];
|
||||
|
||||
export function flush_boundary_micro_tasks() {
|
||||
const tasks = queued_boundary_microtasks.slice();
|
||||
queued_boundary_microtasks = [];
|
||||
function run_boundary_micro_tasks() {
|
||||
var tasks = boundary_micro_tasks;
|
||||
boundary_micro_tasks = [];
|
||||
run_all(tasks);
|
||||
}
|
||||
|
||||
export function flush_post_micro_tasks() {
|
||||
const tasks = queued_post_microtasks.slice();
|
||||
queued_post_microtasks = [];
|
||||
function run_post_micro_tasks() {
|
||||
var tasks = micro_tasks;
|
||||
micro_tasks = [];
|
||||
run_all(tasks);
|
||||
}
|
||||
|
||||
export function flush_idle_tasks() {
|
||||
if (is_idle_task_queued) {
|
||||
is_idle_task_queued = false;
|
||||
const tasks = queued_idle_tasks.slice();
|
||||
queued_idle_tasks = [];
|
||||
run_all(tasks);
|
||||
}
|
||||
function run_idle_tasks() {
|
||||
var tasks = idle_tasks;
|
||||
idle_tasks = [];
|
||||
run_all(tasks);
|
||||
}
|
||||
|
||||
function flush_all_micro_tasks() {
|
||||
if (is_micro_task_queued) {
|
||||
is_micro_task_queued = false;
|
||||
flush_boundary_micro_tasks();
|
||||
flush_post_micro_tasks();
|
||||
}
|
||||
function run_micro_tasks() {
|
||||
run_boundary_micro_tasks();
|
||||
run_post_micro_tasks();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {() => void} fn
|
||||
*/
|
||||
export function queue_boundary_micro_task(fn) {
|
||||
if (!is_micro_task_queued) {
|
||||
is_micro_task_queued = true;
|
||||
queueMicrotask(flush_all_micro_tasks);
|
||||
if (boundary_micro_tasks.length === 0 && micro_tasks.length === 0) {
|
||||
queueMicrotask(run_micro_tasks);
|
||||
}
|
||||
queued_boundary_microtasks.push(fn);
|
||||
|
||||
boundary_micro_tasks.push(fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {() => void} fn
|
||||
*/
|
||||
export function queue_micro_task(fn) {
|
||||
if (!is_micro_task_queued) {
|
||||
is_micro_task_queued = true;
|
||||
queueMicrotask(flush_all_micro_tasks);
|
||||
if (boundary_micro_tasks.length === 0 && micro_tasks.length === 0) {
|
||||
queueMicrotask(run_micro_tasks);
|
||||
}
|
||||
queued_post_microtasks.push(fn);
|
||||
|
||||
micro_tasks.push(fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {() => void} fn
|
||||
*/
|
||||
export function queue_idle_task(fn) {
|
||||
if (!is_idle_task_queued) {
|
||||
is_idle_task_queued = true;
|
||||
request_idle_callback(flush_idle_tasks);
|
||||
if (idle_tasks.length === 0) {
|
||||
request_idle_callback(run_idle_tasks);
|
||||
}
|
||||
|
||||
idle_tasks.push(fn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously run any queued tasks.
|
||||
*/
|
||||
export function flush_tasks() {
|
||||
if (boundary_micro_tasks.length > 0 || micro_tasks.length > 0) {
|
||||
run_micro_tasks();
|
||||
}
|
||||
|
||||
if (idle_tasks.length > 0) {
|
||||
run_idle_tasks();
|
||||
}
|
||||
queued_idle_tasks.push(fn);
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
<p class=" svelte-xyz">Foo</p>
|
||||
<p class="svelte-xyz">Foo</p>
|
||||
@ -0,0 +1,43 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
// This test counts mutations on hydration
|
||||
// set_class() should not mutate class on hydration, except if mismatch
|
||||
export default test({
|
||||
mode: ['server', 'hydrate'],
|
||||
|
||||
server_props: {
|
||||
browser: false
|
||||
},
|
||||
|
||||
props: {
|
||||
browser: true
|
||||
},
|
||||
|
||||
html: `
|
||||
<main id="main" class="browser">
|
||||
<div class="custom svelte-1cjqok6 foo bar"></div>
|
||||
<span class="svelte-1cjqok6 foo bar"></span>
|
||||
<b class="custom foo bar"></b>
|
||||
<i class="foo bar"></i>
|
||||
</main>
|
||||
`,
|
||||
|
||||
ssrHtml: `
|
||||
<main id="main">
|
||||
<div class="custom svelte-1cjqok6 foo bar"></div>
|
||||
<span class="svelte-1cjqok6 foo bar"></span>
|
||||
<b class="custom foo bar"></b>
|
||||
<i class="foo bar"></i>
|
||||
</main>
|
||||
`,
|
||||
|
||||
async test({ assert, component, instance }) {
|
||||
flushSync();
|
||||
assert.deepEqual(instance.get_and_clear_mutations(), ['MAIN']);
|
||||
|
||||
component.foo = false;
|
||||
flushSync();
|
||||
assert.deepEqual(instance.get_and_clear_mutations(), ['DIV', 'SPAN', 'B', 'I']);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,47 @@
|
||||
<script>
|
||||
let {
|
||||
classname = 'custom',
|
||||
foo = true,
|
||||
bar = true,
|
||||
browser
|
||||
} = $props();
|
||||
|
||||
let mutations = [];
|
||||
let observer;
|
||||
|
||||
if (browser) {
|
||||
observer = new MutationObserver(update_mutation_records);
|
||||
observer.observe(document.querySelector('#main'), { attributes: true, subtree: true });
|
||||
|
||||
$effect(() => {
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
}
|
||||
|
||||
function update_mutation_records(results) {
|
||||
for (const r of results) {
|
||||
mutations.push(r.target.nodeName);
|
||||
}
|
||||
}
|
||||
|
||||
export function get_and_clear_mutations() {
|
||||
update_mutation_records(observer.takeRecords());
|
||||
const result = mutations;
|
||||
mutations = [];
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
|
||||
<main id="main" class:browser>
|
||||
<div class={classname} class:foo class:bar></div>
|
||||
<span class:foo class:bar></span>
|
||||
<b class={classname} class:foo class:bar></b>
|
||||
<i class:foo class:bar></i>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
div,
|
||||
span {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
@ -0,0 +1,145 @@
|
||||
import { flushSync } from 'svelte';
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({
|
||||
html: `
|
||||
<div class="svelte-tza1s0"></div>
|
||||
<span></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="foo svelte-tza1s0"></div>
|
||||
<span class="foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="foo svelte-tza1s0"></span></div>
|
||||
|
||||
|
||||
<div class="foo svelte-tza1s0 bar"></div>
|
||||
<span class="foo bar"></span>
|
||||
<div class="svelte-tza1s0"><span class="foo svelte-tza1s0 bar"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0"></div>
|
||||
<span></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0 bar"></div>
|
||||
<span class="bar"></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0 bar"></span></div>
|
||||
|
||||
<div class="football svelte-tza1s0 bar"></div>
|
||||
<span class="football bar"></span>
|
||||
<div class="svelte-tza1s0"><span class="football svelte-tza1s0 bar"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0 bar not-foo"></div>
|
||||
<span class="bar not-foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0 bar not-foo"></span></div>
|
||||
|
||||
`,
|
||||
test({ assert, target, component }) {
|
||||
component.foo = true;
|
||||
flushSync();
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<div class="svelte-tza1s0"></div>
|
||||
<span></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="foo svelte-tza1s0"></div>
|
||||
<span class="foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="foo svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="foo svelte-tza1s0 bar"></div>
|
||||
<span class="foo bar"></span>
|
||||
<div class="svelte-tza1s0"><span class="foo svelte-tza1s0 bar"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0 foo"></div>
|
||||
<span class="foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0 foo"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0 bar foo"></div>
|
||||
<span class="bar foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0 bar foo"></span></div>
|
||||
|
||||
<div class="football svelte-tza1s0 bar foo"></div>
|
||||
<span class="football bar foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="football svelte-tza1s0 bar foo"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0 bar foo"></div>
|
||||
<span class="bar foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0 bar foo"></span></div>
|
||||
`
|
||||
);
|
||||
|
||||
component.bar = false;
|
||||
flushSync();
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<div class="svelte-tza1s0"></div>
|
||||
<span></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="foo svelte-tza1s0"></div>
|
||||
<span class="foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="foo svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="foo svelte-tza1s0"></div>
|
||||
<span class="foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="foo svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0 foo"></div>
|
||||
<span class="foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0 foo"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0 foo"></div>
|
||||
<span class="foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0 foo"></span></div>
|
||||
|
||||
<div class="football svelte-tza1s0 foo"></div>
|
||||
<span class="football foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="football svelte-tza1s0 foo"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0 foo"></div>
|
||||
<span class="foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0 foo"></span></div>
|
||||
`
|
||||
);
|
||||
|
||||
component.foo = false;
|
||||
flushSync();
|
||||
|
||||
assert.htmlEqual(
|
||||
target.innerHTML,
|
||||
`
|
||||
<div class="svelte-tza1s0"></div>
|
||||
<span></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="foo svelte-tza1s0"></div>
|
||||
<span class="foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="foo svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="foo svelte-tza1s0"></div>
|
||||
<span class="foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="foo svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0"></div>
|
||||
<span class=""></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0"></div>
|
||||
<span class=""></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="football svelte-tza1s0"></div>
|
||||
<span class="football"></span>
|
||||
<div class="svelte-tza1s0"><span class="football svelte-tza1s0"></span></div>
|
||||
|
||||
<div class="svelte-tza1s0 not-foo"></div>
|
||||
<span class="not-foo"></span>
|
||||
<div class="svelte-tza1s0"><span class="svelte-tza1s0 not-foo"></span></div>
|
||||
`
|
||||
);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,40 @@
|
||||
<script>
|
||||
let { foo = false, bar = true } = $props();
|
||||
</script>
|
||||
|
||||
<div></div>
|
||||
<span></span>
|
||||
<div><span></span></div>
|
||||
|
||||
<div class="foo"></div>
|
||||
<span class="foo"></span>
|
||||
<div><span class="foo"></span></div>
|
||||
|
||||
<div class="foo" class:bar></div>
|
||||
<span class="foo" class:bar></span>
|
||||
<div><span class="foo" class:bar></span></div>
|
||||
|
||||
<div class="foo" class:foo></div>
|
||||
<span class="foo" class:foo></span>
|
||||
<div><span class="foo" class:foo></span></div>
|
||||
|
||||
<div class="foo" class:bar class:foo></div>
|
||||
<span class="foo" class:bar class:foo></span>
|
||||
<div><span class="foo" class:bar class:foo></span></div>
|
||||
|
||||
<div class="football" class:bar class:foo></div>
|
||||
<span class="football" class:bar class:foo></span>
|
||||
<div><span class="football" class:bar class:foo></span></div>
|
||||
|
||||
<div class="foo" class:bar class:foo class:not-foo={!foo}></div>
|
||||
<span class="foo" class:bar class:foo class:not-foo={!foo}></span>
|
||||
<div><span class="foo" class:bar class:foo class:not-foo={!foo}></span></div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
color: red;
|
||||
}
|
||||
div > span {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@ -1,3 +0,0 @@
|
||||
import { test } from '../../test';
|
||||
|
||||
export default test({});
|
||||
@ -0,0 +1 @@
|
||||
[]
|
||||
@ -0,0 +1,3 @@
|
||||
{#key 'key'}
|
||||
{@const foo = 'bar'}
|
||||
{/key}
|
||||
Loading…
Reference in new issue