minor style tweaks

pull/15352/head
Rich Harris 2 years ago
parent ec4709f1d1
commit 2704f6135d

@ -777,6 +777,7 @@ export function analyze_component(root, source, options) {
let has_class = false;
let has_spread = false;
let has_class_directive = false;
for (const attribute of node.attributes) {
// The spread method appends the hash to the end of the class attribute on its own
if (attribute.type === 'SpreadAttribute') {

@ -515,13 +515,17 @@ function setup_select_synchronization(value_binding, context) {
*/
export function build_class_directives_object(class_directives, context) {
let properties = [];
for (const d of class_directives) {
let expression = /** @type Expression */ (context.visit(d.expression));
if (d.metadata.expression.has_call) {
expression = get_expression_id(context.state, expression);
}
properties.push(b.init(d.name, expression));
}
return b.object(properties);
}

@ -91,6 +91,7 @@ export function SvelteElement(node, context) {
context,
(value, metadata) => (metadata.has_call ? get_expression_id(context.state, value) : value)
);
is_attributes_reactive = build_set_class(
node,
element_id,

@ -78,6 +78,7 @@ export function build_set_attributes(
build_class_directives_object(class_directives, context)
)
);
is_dynamic ||=
class_directives.find((directive) => directive.metadata.expression.has_state) !== null;
}
@ -216,14 +217,17 @@ export function build_set_class(
/** @type {Identifier | undefined} */
let previous_id;
/** @type {ObjectExpression | Identifier | undefined} */
let prev;
/** @type {ObjectExpression | undefined} */
let next;
if (class_directives.length) {
next = build_class_directives_object(class_directives, context);
has_state ||= class_directives.some((d) => d.metadata.expression.has_state);
has_state = has_state || !!class_directives.find((d) => d.metadata.expression.has_state);
if (has_state) {
previous_id = b.id(context.state.scope.generate('classes'));
context.state.init.push(b.declaration('let', [b.declarator(previous_id)]));
@ -235,6 +239,7 @@ export function build_set_class(
/** @type {Expression | undefined} */
let css_hash;
if (element.metadata.scoped && context.state.analysis.css.hash) {
if (value.type === 'Literal' && (value.value === '' || value.value === null)) {
value = b.literal(context.state.analysis.css.hash);
@ -244,6 +249,7 @@ export function build_set_class(
css_hash = b.literal(context.state.analysis.css.hash);
}
}
if (!css_hash && next) {
css_hash = b.null;
}
@ -264,11 +270,12 @@ export function build_set_class(
}
const update = b.stmt(set_class);
if (has_state) {
context.state.update.push(update);
return true;
} else {
context.state.init.push(update);
return false;
}
context.state.init.push(update);
return false;
}

@ -390,6 +390,7 @@ function build_to_class(hash, class_directives, class_attribute) {
/** @type {ObjectExpression | undefined} */
let classes;
if (class_directives.length) {
classes = b.object(
class_directives.map((directive) =>
@ -400,6 +401,7 @@ function build_to_class(hash, class_directives, class_attribute) {
/** @type {Expression} */
let class_name;
if (class_attribute.value === true) {
class_name = b.literal('');
} else if (Array.isArray(class_attribute.value)) {
@ -431,6 +433,7 @@ function build_to_class(hash, class_directives, class_attribute) {
} else {
expression = b.call('$.to_class', class_name, b.literal(hash), classes);
}
class_attribute.value = {
type: 'ExpressionTag',
start: -1,
@ -440,6 +443,7 @@ function build_to_class(hash, class_directives, class_attribute) {
expression: create_expression_metadata()
}
};
return class_attribute;
}

@ -327,9 +327,7 @@ export function set_attributes(
}
var prev_value = current[key];
if (value === prev_value && key !== 'class') {
continue;
}
if (value === prev_value && key !== 'class') continue;
current[key] = value;

@ -3,12 +3,12 @@ import { hydrating } from '../hydration.js';
/**
* @param {Element} dom
* @param {boolean|number} is_html
* @param {string|null} value
* @param {boolean | number} is_html
* @param {string | null} value
* @param {string} [hash]
* @param {Record<string,boolean>} [prev_classes]
* @param {Record<string,boolean>} [next_classes]
* @returns {Record<string,boolean>|undefined}
* @param {Record<string, boolean>} [prev_classes]
* @param {Record<string, boolean>} [next_classes]
* @returns {Record<string, boolean> | undefined}
*/
export function set_class(dom, is_html, value, hash, prev_classes, next_classes) {
// @ts-expect-error need to add __className to patched prototype
@ -16,6 +16,7 @@ export function set_class(dom, is_html, value, hash, prev_classes, next_classes)
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
@ -29,12 +30,15 @@ export function set_class(dom, is_html, value, hash, prev_classes, next_classes)
dom.setAttribute('class', next_class_name);
}
}
// @ts-expect-error need to add __className to patched prototype
dom.__className = value;
} else if (next_classes) {
prev_classes = prev_classes ?? {};
for (const key in next_classes) {
const is_present = !!next_classes[key];
if (is_present !== !!prev_classes[key]) {
// TODO : use dom.classList.toggle instead ?
if (is_present) {
@ -45,5 +49,6 @@ export function set_class(dom, is_html, value, hash, prev_classes, next_classes)
}
}
}
return next_classes;
}

@ -10,7 +10,6 @@ import {
ELEMENT_PRESERVE_ATTRIBUTE_CASE,
ELEMENT_IS_NAMESPACED
} from '../../constants.js';
import { escape_html } from '../../escaping.js';
import { DEV } from 'esm-env';
import { current_component, pop, push } from './context.js';
@ -198,7 +197,7 @@ export function css_props(payload, is_html, props, component, dynamic = false) {
/**
* @param {Record<string, unknown>} attrs
* @param {string|null} css_hash
* @param {string | null} css_hash
* @param {Record<string, boolean>} [classes]
* @param {Record<string, string>} [styles]
* @param {number} [flags]
@ -214,6 +213,7 @@ export function spread_attributes(attrs, css_hash, classes, styles, flags = 0) {
if (attrs.class) {
attrs.class = clsx(attrs.class);
}
if (css_hash || classes) {
attrs.class = to_class(attrs.class, css_hash, classes);
}

@ -49,19 +49,24 @@ export function clsx(value) {
*/
export function to_class(value, hash, classes) {
let class_name = value == null ? '' : '' + value;
if (hash) {
class_name = class_name ? class_name + ' ' + hash : hash;
}
if (classes) {
const white_spaces = ' \t\n\r\f\u00a0\u000b\ufeff';
for (const key in classes) {
if (classes[key]) {
class_name = class_name ? class_name + ' ' + key : key;
} else if (class_name.length) {
const len = key.length;
let start = 0;
while ((start = class_name.indexOf(key, start)) >= 0) {
let stop = start + len;
if (
white_spaces.indexOf(class_name[start - 1] ?? ' ') >= 0 &&
white_spaces.indexOf(class_name[stop] ?? ' ') >= 0
@ -78,8 +83,6 @@ export function to_class(value, hash, classes) {
}
}
}
if (class_name === '') {
return null;
}
return class_name;
return class_name === '' ? null : class_name;
}

@ -1,9 +1,11 @@
import { flushSync } from 'svelte';
import { test } from '../../test';
// This test count mutations on hydration
// This test counts mutations on hydration
// set_class() shoult not mutate class on hydration, except if mismatch
export default test({
mode: ['server', 'hydrate'],
server_props: {
browser: false
},
@ -13,32 +15,29 @@ export default test({
},
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>
`,
<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>
`,
<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, variant }) {
// only on hydration
if (variant === 'hydrate') {
flushSync();
assert.deepEqual(instance.get_and_clear_mutations(), ['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']);
}
component.foo = false;
flushSync();
assert.deepEqual(instance.get_and_clear_mutations(), ['DIV', 'SPAN', 'B', 'I']);
}
});

@ -2,40 +2,37 @@
import { onDestroy } from "svelte";
let {
clazz = 'custom',
foo = true,
bar = true,
browser
} = $props();
clazz = 'custom',
foo = true,
bar = true,
browser
} = $props();
let mutations = [];
let observer;
let mutations = [];
let observer;
if (browser) {
if (browser) {
observer = new MutationObserver(updateMutationRecords);
const main = document.querySelector('main#main');
if (main) {
observer.observe(main, { attributes: true, subtree: true });
}
const main = document.querySelector('main#main');
if (main) {
observer.observe(main, { attributes: true, subtree: true });
}
}
function updateMutationRecords(results) {
for (const r of results) {
function updateMutationRecords(results) {
for (const r of results) {
mutations.push(r.target.nodeName);
}
}
export function get_and_clear_mutations() {
updateMutationRecords(observer.takeRecords());
const result = mutations;
mutations = [];
return result;
}
}
onDestroy(() => { if (observer) observer.disconnect(); });
export function get_and_clear_mutations() {
updateMutationRecords(observer.takeRecords());
const result = mutations;
mutations = [];
return result;
}
onDestroy(() => { if (observer) observer.disconnect(); });
</script>
<main id="main" class:browser>

@ -2,7 +2,6 @@
let { foo = false, bar = true } = $props();
</script>
<div></div>
<span></span>
<div><span></span></div>
@ -11,7 +10,6 @@
<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>
@ -39,4 +37,4 @@
div > span {
font-weight: bold;
}
</style>
</style>

Loading…
Cancel
Save