Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/css-attribute-case-insensitive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'svelte': patch
---

fix: treat CSS attribute selectors as case-insensitive for HTML enumerated attributes

CSS attribute selectors for HTML enumerated attributes (like `method`, `type`, `dir`, etc.) should match case-insensitively, matching browser behavior. Previously, `form[method="get"]` would not match `<form method="GET">`, causing the selector to be incorrectly pruned as unused and the scoping class to not be applied.
48 changes: 47 additions & 1 deletion packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,50 @@ const whitelist_attribute_selector = new Map([
['dialog', ['open']]
]);

/**
* HTML attributes whose enumerated values are case-insensitive per the HTML spec.
* CSS attribute selectors match these values case-insensitively in HTML documents.
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/Attribute_selectors#description
*/
const case_insensitive_attributes = new Set([
'accept-charset',
'autocapitalize',
'autocomplete',
'behavior',
'charset',
'crossorigin',
'decoding',
'dir',
'direction',
'draggable',
'enctype',
'enterkeyhint',
'fetchpriority',
'formenctype',
'formmethod',
'formtarget',
'hidden',
'http-equiv',
'inputmode',
'kind',
'loading',
'method',
'preload',
'referrerpolicy',
'rel',
'rev',
'role',
'rules',
'scope',
'shape',
'spellcheck',
'target',
'translate',
'type',
'valign',
'wrap'
]);

/** @type {Compiler.AST.CSS.Combinator} */
const descendant_combinator = {
type: 'Combinator',
Expand Down Expand Up @@ -523,7 +567,9 @@ function relative_selector_might_apply_to_node(relative_selector, rule, element,
selector.name,
selector.value && unquote(selector.value),
selector.matcher,
selector.flags?.includes('i') ?? false
((selector.flags?.includes('i') ?? false) ||
(!selector.flags?.includes('s') &&
case_insensitive_attributes.has(selector.name.toLowerCase())))
)
) {
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
form[method="get"].svelte-xyz h1:where(.svelte-xyz) {
color: red;
}

form[method="post"].svelte-xyz h1:where(.svelte-xyz) {
color: blue;
}

input[type="text"].svelte-xyz {
color: green;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<form class="svelte-xyz" method="GET"><h1 class="svelte-xyz">Hello</h1></form> <form class="svelte-xyz" method="POST"><h1 class="svelte-xyz">World</h1></form> <input class="svelte-xyz" type="Text" />
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<form method="GET">
<h1>Hello</h1>
</form>

<form method="POST">
<h1>World</h1>
</form>

<input type="Text" />

<style>
form[method="get"] h1 {
color: red;
}

form[method="post"] h1 {
color: blue;
}

input[type="text"] {
color: green;
}
</style>