diff --git a/.changeset/css-attribute-case-insensitive.md b/.changeset/css-attribute-case-insensitive.md new file mode 100644 index 0000000000..e5b3bcea2b --- /dev/null +++ b/.changeset/css-attribute-case-insensitive.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +fix: treat CSS attribute selectors as case-insensitive for HTML enumerated attributes diff --git a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js index 7242c69d8c..7e05d2e7d3 100644 --- a/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js +++ b/packages/svelte/src/compiler/phases/2-analyze/css/css-prune.js @@ -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 {@link https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors HTML spec} + */ +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', @@ -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; diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css new file mode 100644 index 0000000000..01ec1b2269 --- /dev/null +++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.css @@ -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; + } diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html new file mode 100644 index 0000000000..101c03f836 --- /dev/null +++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/expected.html @@ -0,0 +1 @@ +
diff --git a/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte new file mode 100644 index 0000000000..e11a922d75 --- /dev/null +++ b/packages/svelte/tests/css/samples/attribute-selector-html-case-insensitive/input.svelte @@ -0,0 +1,23 @@ + + + + + + +