Skip to content

[FEATURE] Parse color functions with modern syntax #800

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 23, 2025
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ Please also have a look at our

### Added

- Partial support for CSS Color Module Level 4 syntax:
- Partial support for CSS Color Module Level 4:
- `rgb` and `rgba`, and `hsl` and `hsla` are now aliases (#797}
- Parse color functions that use the "modern" syntax
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Parse color functions that use the "modern" syntax
- Parse color functions that use the "modern" syntax (#800)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd intended to do this once I'd determined what the PR number would be, but then forgot. Now added.

- Add official support for PHP 8.4 (#657)
- Support arithmetic operators in CSS function arguments (#607)
- Add support for inserting an item in a CSS list (#545)
Expand Down
34 changes: 33 additions & 1 deletion src/Value/Color.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ private static function parseColorFunction(ParserState $oParserState): CSSFuncti
}

$bContainsVar = false;
$isLegacySyntax = false;
$iLength = $oParserState->strlen($colorModeForParsing);
for ($i = 0; $i < $iLength; ++$i) {
$oParserState->consumeWhiteSpace();
Expand All @@ -138,9 +139,40 @@ private static function parseColorFunction(ParserState $oParserState): CSSFuncti
break;
}

if ($i < ($iLength - 1)) {
// "Legacy" syntax is comma-delimited.
// "Modern" syntax is space-delimited, with `/` as alpha delimiter.
// They cannot be mixed.
if ($i === 0) {
// An immediate closing parenthesis is not valid.
if ($oParserState->comes(')')) {
throw new UnexpectedTokenException(
'Color function with no arguments',
'',
'custom',
$oParserState->currentLine()
);
}
$isLegacySyntax = $oParserState->comes(',');
}

if ($isLegacySyntax && $i < ($iLength - 1)) {
$oParserState->consume(',');
}

// In the "modern" syntax, the alpha value must be delimited with `/`.
if (!$isLegacySyntax) {
if ($bContainsVar) {
// If the `var` substitution encompasses more than one argument,
// the alpha deliminator may come at any time.
if ($oParserState->comes('/')) {
$oParserState->consume('/');
}
} elseif (($colorModeForParsing[$i + 1] ?? '') === 'a') {
// Alpha value is the next expected argument.
// Since a closing parenthesis was not found, a `/` separator is now required.
$oParserState->consume('/');
}
}
}
$oParserState->consume(')');

Expand Down
164 changes: 162 additions & 2 deletions tests/Unit/Value/ColorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,142 @@ public static function provideValidColorAndExpectedRendering(): array
'rgb(0, 119, 0, 0.5)',
'rgba(0,119,0,.5)',
],
/*
'modern rgb' => [
'rgb(0 119 0)',
'rgb(0,119,0)',
'#070',
],
/*
'modern rgb with none' => [
'rgb(none 119 0)',
'rgb(none 119 0)',
],
//*/
'modern rgba' => [
'rgb(0 119 0 / 0.5)',
'rgba(0,119,0,.5)',
],
/*
'modern rgba with none as alpha' => [
'rgb(0 119 0 / none)',
'rgba(0 119 0 / none)',
],
//*/
'legacy rgb with var for R' => [
'rgb(var(--r), 119, 0)',
'rgb(var(--r),119,0)',
],
'legacy rgb with var for G' => [
'rgb(0, var(--g), 0)',
'rgb(0,var(--g),0)',
],
'legacy rgb with var for B' => [
'rgb(0, 119, var(--b))',
'rgb(0,119,var(--b))',
],
'legacy rgb with var for RG' => [
'rgb(var(--rg), 0)',
'rgb(var(--rg),0)',
],
'legacy rgb with var for GB' => [
'rgb(0, var(--gb))',
'rgb(0,var(--gb))',
],
'legacy rgba with var for R' => [
'rgba(var(--r), 119, 0, 0.5)',
'rgba(var(--r),119,0,.5)',
],
'legacy rgba with var for G' => [
'rgba(0, var(--g), 0, 0.5)',
'rgba(0,var(--g),0,.5)',
],
'legacy rgba with var for B' => [
'rgb(0, 119, var(--b), 0.5)',
'rgb(0,119,var(--b),.5)',
],
'legacy rgba with var for A' => [
'rgba(0, 119, 0, var(--a))',
'rgba(0,119,0,var(--a))',
],
'legacy rgba with var for RG' => [
'rgba(var(--rg), 0, 0.5)',
'rgba(var(--rg),0,.5)',
],
'legacy rgba with var for GB' => [
'rgba(0, var(--gb), 0.5)',
'rgba(0,var(--gb),.5)',
],
'legacy rgba with var for BA' => [
'rgba(0, 119, var(--ba))',
'rgba(0,119,var(--ba))',
],
'legacy rgba with var for RGB' => [
'rgba(var(--rgb), 0.5)',
'rgba(var(--rgb),.5)',
],
'legacy rgba with var for GBA' => [
'rgba(0, var(--gba))',
'rgba(0,var(--gba))',
],
'modern rgb with var for R' => [
'rgb(var(--r) 119 0)',
'rgb(var(--r),119,0)',
],
'modern rgb with var for G' => [
'rgb(0 var(--g) 0)',
'rgb(0,var(--g),0)',
],
'modern rgb with var for B' => [
'rgb(0 119 var(--b))',
'rgb(0,119,var(--b))',
],
'modern rgb with var for RG' => [
'rgb(var(--rg) 0)',
'rgb(var(--rg),0)',
],
'modern rgb with var for GB' => [
'rgb(0 var(--gb))',
'rgb(0,var(--gb))',
],
'modern rgba with var for R' => [
'rgba(var(--r) 119 0 / 0.5)',
'rgba(var(--r),119,0,.5)',
],
'modern rgba with var for G' => [
'rgba(0 var(--g) 0 / 0.5)',
'rgba(0,var(--g),0,.5)',
],
'modern rgba with var for B' => [
'rgba(0 119 var(--b) / 0.5)',
'rgba(0,119,var(--b),.5)',
],
'modern rgba with var for A' => [
'rgba(0 119 0 / var(--a))',
'rgba(0,119,0,var(--a))',
],
'modern rgba with var for RG' => [
'rgba(var(--rg) 0 / 0.5)',
'rgba(var(--rg),0,.5)',
],
'modern rgba with var for GB' => [
'rgba(0 var(--gb) / 0.5)',
'rgba(0,var(--gb),.5)',
],
'modern rgba with var for BA' => [
'rgba(0 119 var(--ba))',
'rgba(0,119,var(--ba))',
],
'modern rgba with var for RGB' => [
'rgba(var(--rgb) / 0.5)',
'rgba(var(--rgb),.5)',
],
'modern rgba with var for GBA' => [
'rgba(0 var(--gba))',
'rgba(0,var(--gba))',
],
'rgba with var for RGBA' => [
'rgba(var(--rgba))',
'rgba(var(--rgba))',
],
'legacy hsl' => [
'hsl(120, 100%, 25%)',
'hsl(120,100%,25%)',
Expand Down Expand Up @@ -120,6 +242,26 @@ public static function provideValidColorAndExpectedRendering(): array
'hsl(120, 100%, 25%, 0.5)',
'hsla(120,100%,25%,.5)',
],
'modern hsl' => [
'hsl(120 100% 25%)',
'hsl(120,100%,25%)',
],
/*
'modern hsl with none' => [
'hsl(none 100% 25%)',
'hsl(none 100% 25%)',
],
//*/
'modern hsla' => [
'hsl(120 100% 25% / 0.5)',
'hsla(120,100%,25%,.5)',
],
/*
'modern hsla with none as alpha' => [
'hsl(120 100% 25% none)',
'hsla(120 100% 25% none)',
],
//*/
];
}

Expand Down Expand Up @@ -180,6 +322,15 @@ public static function provideInvalidColor(): array
'rgb(255, 0px, 0)',
],
//*/
'modern rgb color without slash separator for alpha' => [
'rgb(255 0 0 0.5)',
],
'rgb color with mixed separators, comma first' => [
'rgb(255, 0 0)',
],
'rgb color with mixed separators, space first' => [
'rgb(255 0, 0)',
],
'hsl color with 0 arguments' => [
'hsl()',
],
Expand All @@ -200,6 +351,15 @@ public static function provideInvalidColor(): array
'hsl(0px, 100%, 50%)'
],
//*/
'modern hsl color without slash separator for alpha' => [
'rgb(0 100% 50% 0.5)',
],
'hsl color with mixed separators, comma first' => [
'hsl(0, 100% 50%)',
],
'hsl color with mixed separators, space first' => [
'hsl(0 100%, 50%)',
],
];
}

Expand Down