diff --git a/src/css/selectors/parser.rs b/src/css/selectors/parser.rs index 9404d01ccbc8..afd15d0debb0 100644 --- a/src/css/selectors/parser.rs +++ b/src/css/selectors/parser.rs @@ -949,6 +949,12 @@ pub enum PseudoClass { Target, /// The [:target-within](https://drafts.csswg.org/selectors-4/#the-target-within-pseudo) pseudo class. TargetWithin, + /// The [:target-current](https://drafts.csswg.org/css-overflow-5/#selectordef-target-current) pseudo class. + TargetCurrent, + /// The [:target-before](https://drafts.csswg.org/css-overflow-5/#selectordef-target-before) pseudo class. + TargetBefore, + /// The [:target-after](https://drafts.csswg.org/css-overflow-5/#selectordef-target-after) pseudo class. + TargetAfter, /// The [:visited](https://drafts.csswg.org/selectors-4/#visited-pseudo) pseudo class. Visited, @@ -1482,6 +1488,10 @@ fn lookup_non_ts_pseudo_class(name: &[u8]) -> Option { b"local-link" => P::LocalLink, b"target" => P::Target, b"target-within" => P::TargetWithin, + // https://drafts.csswg.org/css-overflow-5/#active-before-after-scroll-markers + b"target-current" => P::TargetCurrent, + b"target-before" => P::TargetBefore, + b"target-after" => P::TargetAfter, b"visited" => P::Visited, // https://drafts.csswg.org/selectors-4/#input-pseudos b"enabled" => P::Enabled, diff --git a/src/css/selectors/selector.rs b/src/css/selectors/selector.rs index 3109fdd519d9..31cbb15b3a5d 100644 --- a/src/css/selectors/selector.rs +++ b/src/css/selectors/selector.rs @@ -1081,6 +1081,11 @@ pub(crate) mod serialize { PseudoClass::LocalLink => dest.write_str(b":local-link")?, PseudoClass::Target => dest.write_str(b":target")?, PseudoClass::TargetWithin => dest.write_str(b":target-within")?, + + // https://drafts.csswg.org/css-overflow-5/#active-before-after-scroll-markers + PseudoClass::TargetCurrent => dest.write_str(b":target-current")?, + PseudoClass::TargetBefore => dest.write_str(b":target-before")?, + PseudoClass::TargetAfter => dest.write_str(b":target-after")?, PseudoClass::Visited => dest.write_str(b":visited")?, // https://drafts.csswg.org/selectors-4/#input-pseudos diff --git a/test/bundler/css/target-current-pseudo-classes-41120.test.ts b/test/bundler/css/target-current-pseudo-classes-41120.test.ts new file mode 100644 index 000000000000..e768b81c4e19 --- /dev/null +++ b/test/bundler/css/target-current-pseudo-classes-41120.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, test } from "bun:test"; +import { tempDir } from "harness"; +import path from "node:path"; + +// https://github.com/oven-sh/bun/issues/41120 +// :target-current, :target-before and :target-after are css-overflow-5 +// pseudo-classes, and lightningcss carries all three. Bun's table did not, so +// they parsed as unknown custom pseudo-classes and were emitted verbatim. +// +// A pseudo-class name is ASCII case-insensitive, so the canonical spelling is +// the observable difference: :target-within, which Bun already knows, is +// lowercased today and sits in this fixture as the control for what the other +// three should do. +describe("css", () => { + test("the scroll navigation control pseudo-classes are recognized (#41120)", async () => { + using dir = tempDir("css-41120-target", { + "in.css": ` + .a:TARGET-CURRENT { color: red } + .b:Target-Before { color: green } + .c:target-AFTER { color: teal } + .d:TARGET-WITHIN { color: olive } + li:target-current { color: red } + `, + }); + const result = await Bun.build({ + entrypoints: [path.join(String(dir), "in.css")], + minify: true, + throw: true, + }); + expect(result.logs.map(String)).toEqual([]); + const out = await result.outputs[0].text(); + expect(out.trim()).toBe( + ".a:target-current{color:red}.b:target-before{color:green}.c:target-after{color:teal}" + + ".d:target-within{color:olive}li:target-current{color:red}", + ); + }); +});