diff --git a/.changeset/css-props-falsy-values.md b/.changeset/css-props-falsy-values.md new file mode 100644 index 0000000000..677594ddaf --- /dev/null +++ b/.changeset/css-props-falsy-values.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: apply CSS custom properties with falsy values on components diff --git a/.changeset/quiet-donkeys-clap.md b/.changeset/quiet-donkeys-clap.md new file mode 100644 index 0000000000..dc9df3b7e1 --- /dev/null +++ b/.changeset/quiet-donkeys-clap.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: increment private state fields through a non-`this` receiver diff --git a/packages/svelte/CHANGELOG.md b/packages/svelte/CHANGELOG.md index 722fc21052..5ec2101bf7 100644 --- a/packages/svelte/CHANGELOG.md +++ b/packages/svelte/CHANGELOG.md @@ -1,5 +1,17 @@ # svelte +## 5.56.9 + +### Patch Changes + +- fix: skip controlled each fast path while another batch is pending ([#18625](https://github.com/sveltejs/svelte/pull/18625)) + +- fix: better whitespace handling inside printer ([#18638](https://github.com/sveltejs/svelte/pull/18638)) + +- fix: don't duplicate comments in attributes ([#18636](https://github.com/sveltejs/svelte/pull/18636)) + +- fix: preserve CSS comments in the AST printer ([#18637](https://github.com/sveltejs/svelte/pull/18637)) + ## 5.56.8 ### Patch Changes diff --git a/packages/svelte/package.json b/packages/svelte/package.json index cae334a4b0..a6637e2744 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -2,7 +2,7 @@ "name": "svelte", "description": "Cybernetically enhanced web apps", "license": "MIT", - "version": "5.56.8", + "version": "5.56.9", "type": "module", "types": "./types/index.d.ts", "engines": { diff --git a/packages/svelte/src/compiler/index.js b/packages/svelte/src/compiler/index.js index a18e8bb864..d40b8b8935 100644 --- a/packages/svelte/src/compiler/index.js +++ b/packages/svelte/src/compiler/index.js @@ -176,7 +176,8 @@ export function parseCss(source) { type: 'StyleSheetFile', start: 0, end: source.length, - children + children, + comments: parser.css_comments }; } diff --git a/packages/svelte/src/compiler/phases/1-parse/index.js b/packages/svelte/src/compiler/phases/1-parse/index.js index 02d1629c99..fa7b292640 100644 --- a/packages/svelte/src/compiler/phases/1-parse/index.js +++ b/packages/svelte/src/compiler/phases/1-parse/index.js @@ -50,6 +50,9 @@ export class Parser { /** */ index = 0; + /** @type {AST.CSS.CSSComment[]} */ + css_comments = []; + /** * Creates a minimal parser instance for CSS-only parsing. * Skips Svelte component parsing setup. @@ -61,6 +64,7 @@ export class Parser { parser.template = source; parser.index = 0; parser.loose = false; + parser.css_comments = []; return parser; } diff --git a/packages/svelte/src/compiler/phases/1-parse/read/style.js b/packages/svelte/src/compiler/phases/1-parse/read/style.js index 160e5da277..5eb8c2b3e9 100644 --- a/packages/svelte/src/compiler/phases/1-parse/read/style.js +++ b/packages/svelte/src/compiler/phases/1-parse/read/style.js @@ -24,6 +24,7 @@ const REGEX_HTML_COMMENT_CLOSE = /-->/; */ export default function read_style(parser, start, attributes) { const content_start = parser.index; + parser.css_comments = []; const children = read_body(parser, (p) => p.match('= p.template.length); const content_end = parser.index; @@ -36,6 +37,7 @@ export default function read_style(parser, start, attributes) { end: parser.index, attributes, children, + comments: parser.css_comments, content: { start: content_start, end: content_end, @@ -229,18 +231,22 @@ function read_selector(parser, inside_pseudo_class = false) { end: parser.index }); } else if (parser.eat('::')) { + const name = read_identifier(parser); + /** @type {AST.CSS.SelectorList | null} */ + let args = null; + + if (parser.eat('(')) { + args = read_selector_list(parser, true); + parser.eat(')', true); + } + relative_selector.selectors.push({ type: 'PseudoElementSelector', - name: read_identifier(parser), + name, start, - end: parser.index + end: parser.index, + ...(args && { args }) }); - // We read the inner selectors of a pseudo element to ensure it parses correctly, - // but we don't do anything with the result. - if (parser.eat('(')) { - read_selector_list(parser, true); - parser.eat(')', true); - } } else if (parser.eat(':')) { const name = read_identifier(parser); @@ -323,7 +329,7 @@ function read_selector(parser, inside_pseudo_class = false) { } const index = parser.index; - allow_comment_or_whitespace(parser); + allow_comment_or_whitespace(parser, false); if (parser.match(',') || (inside_pseudo_class ? parser.match(')') : parser.match('{'))) { // rewind, so we know whether to continue building the selector list @@ -449,7 +455,7 @@ function read_block_item(parser) { // read ahead to understand whether we're dealing with a declaration or a nested rule. // this involves some duplicated work, but avoids a try-catch that would disguise errors const start = parser.index; - read_value(parser); + read_value(parser, false); const char = parser.template[parser.index]; parser.index = start; @@ -492,10 +498,13 @@ function read_declaration(parser) { /** * @param {Parser} parser + * @param {boolean} [capture_comments] * @returns {string} */ -function read_value(parser) { +function read_value(parser, capture_comments = true) { let value = ''; + /** @type {AST.CSS.CSSComment[]} */ + const value_comments = []; let escaped = false; let in_url = false; @@ -523,6 +532,13 @@ function read_value(parser) { } else if (char === '(' && value.slice(-3) === 'url') { in_url = true; } else if ((char === ';' || char === '{' || char === '}') && !in_url && !quote_mark) { + const leading_whitespace = value.length - value.trimStart().length; + for (const comment of value_comments) { + comment.position = Math.max( + 0, + /** @type {number} */ (comment.position) - leading_whitespace + ); + } return value.trim(); } else if ( char === '/' && @@ -530,13 +546,11 @@ function read_value(parser) { !quote_mark && parser.template[parser.index + 1] === '*' ) { - parser.index += 2; - while (parser.index < parser.template.length) { - if (parser.template[parser.index] === '*' && parser.template[parser.index + 1] === '/') { - parser.index += 2; - break; - } - parser.index++; + const comment = read_comment(parser); + if (capture_comments) { + comment.position = value.length; + parser.css_comments.push(comment); + value_comments.push(comment); } continue; } @@ -624,13 +638,16 @@ function read_identifier(parser) { return identifier; } -/** @param {Parser} parser */ -function allow_comment_or_whitespace(parser) { +/** + * @param {Parser} parser + * @param {boolean} [capture_comments] + */ +function allow_comment_or_whitespace(parser, capture_comments = true) { parser.allow_whitespace(); while (parser.match('/*') || parser.match(' +
+ {#each items as item (item)} + {item} + {/each} +
diff --git a/packages/svelte/tests/runtime-runes/samples/class-private-state-increment-other-receiver/_config.js b/packages/svelte/tests/runtime-runes/samples/class-private-state-increment-other-receiver/_config.js new file mode 100644 index 0000000000..8f023b602a --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-private-state-increment-other-receiver/_config.js @@ -0,0 +1,22 @@ +import { flushSync } from 'svelte'; +import { test } from '../../test'; + +export default test({ + html: ``, + + test({ assert, target }) { + const [bump, drop] = target.querySelectorAll('button'); + + bump?.click(); + flushSync(); + assert.htmlEqual(target.innerHTML, ``); + + bump?.click(); + flushSync(); + assert.htmlEqual(target.innerHTML, ``); + + drop?.click(); + flushSync(); + assert.htmlEqual(target.innerHTML, ``); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/class-private-state-increment-other-receiver/main.svelte b/packages/svelte/tests/runtime-runes/samples/class-private-state-increment-other-receiver/main.svelte new file mode 100644 index 0000000000..67827aceee --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/class-private-state-increment-other-receiver/main.svelte @@ -0,0 +1,23 @@ + + + + diff --git a/packages/svelte/tests/runtime-runes/samples/component-css-props-falsy/Component.svelte b/packages/svelte/tests/runtime-runes/samples/component-css-props-falsy/Component.svelte new file mode 100644 index 0000000000..0fd9beb678 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/component-css-props-falsy/Component.svelte @@ -0,0 +1 @@ +
Hello
diff --git a/packages/svelte/tests/runtime-runes/samples/component-css-props-falsy/_config.js b/packages/svelte/tests/runtime-runes/samples/component-css-props-falsy/_config.js new file mode 100644 index 0000000000..78b14775e5 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/component-css-props-falsy/_config.js @@ -0,0 +1,12 @@ +import { test } from '../../test'; + +export default test({ + ssrHtml: `
Hello
`, + + async test({ assert, target }) { + assert.htmlEqual( + target.innerHTML, + `
Hello
` + ); + } +}); diff --git a/packages/svelte/tests/runtime-runes/samples/component-css-props-falsy/main.svelte b/packages/svelte/tests/runtime-runes/samples/component-css-props-falsy/main.svelte new file mode 100644 index 0000000000..30c029b547 --- /dev/null +++ b/packages/svelte/tests/runtime-runes/samples/component-css-props-falsy/main.svelte @@ -0,0 +1,5 @@ + + + diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 1e1d66be6b..165bc44575 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -1745,6 +1745,15 @@ declare module 'svelte/compiler' { export interface StyleSheetBase extends BaseNode { children: Array; + /** CSS comments in source order */ + comments: CSSComment[]; + } + + export interface CSSComment extends BaseNode { + type: 'CSSComment'; + value: string; + /** Character offset in a containing declaration value or at-rule prelude */ + position?: number; } export interface StyleSheetFile extends StyleSheetBase { @@ -1839,6 +1848,7 @@ declare module 'svelte/compiler' { export interface PseudoElementSelector extends BaseNode { type: 'PseudoElementSelector'; name: string; + args?: SelectorList; } export interface PseudoClassSelector extends BaseNode {