diff --git a/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php b/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php
index 85ecb963..8749a241 100644
--- a/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php
+++ b/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php
@@ -77,12 +77,25 @@ private function inlineLayoutCarrierProperties(): array
);
}
+ /**
+ * @return list
+ */
+ private function inlineListMarkerCarrierProperties(): array
+ {
+ return array(
+ 'list-style',
+ 'list-style-type',
+ 'list-style-position',
+ 'list-style-image',
+ );
+ }
+
/**
* @return list
*/
private function inlineGeometryProperties(): array
{
- return array_merge($this->inlineLayoutCarrierProperties(), array(
+ return array_merge($this->inlineLayoutCarrierProperties(), $this->inlineListMarkerCarrierProperties(), array(
'width',
'height',
'min-width',
@@ -375,7 +388,7 @@ private function inlineGeometryClassName(
}
$rawValue = trim((string) ($declarations[$property] ?? ($forcedDeclarations[$property] ?? '')));
$value = trim(preg_replace('/\s*!\s*important\s*$/i', '', $rawValue) ?? $rawValue);
- if ( in_array($property, array( 'background', 'background-image' ), true) ) {
+ if ( in_array($property, array( 'background', 'background-image', 'list-style', 'list-style-image' ), true) ) {
$value = CssUrlRewriter::rewrite($value, fn (string $url): string => $this->resolvedAssetImageUrl($url));
}
if ('' !== $value && ! preg_match('~[{}<>;]|/\*~', $value)) {
@@ -400,16 +413,18 @@ private function inlineGeometryClassName(
// (forced/custom-property fallbacks) sort last.
$sourceOrder = array_flip(array_keys($declarations));
uksort($geometry, static fn (string $a, string $b): int => (($sourceOrder[$a] ?? PHP_INT_MAX) <=> ($sourceOrder[$b] ?? PHP_INT_MAX)) ?: strcmp($a, $b));
- $layoutDeclarations = array();
+ $normalPriorityDeclarations = array();
$importantDeclarations = array();
$forcedPropertyLookup = array_fill_keys($forcedProperties, true);
$inlineLayoutPropertyLookup = array_fill_keys($this->inlineLayoutCarrierProperties(), true);
+ $inlineListMarkerPropertyLookup = array_fill_keys($this->inlineListMarkerCarrierProperties(), true);
foreach ($geometry as $property => $value) {
- if ( isset($inlineLayoutPropertyLookup[$property]) && ! isset($forcedPropertyLookup[$property]) ) {
- // Preserve source inline layout over a later plain author class
- // without preventing an authored !important declaration from
- // retaining its normal cascade priority.
- $layoutDeclarations[] = $property . ':' . $value;
+ if ( isset($inlineListMarkerPropertyLookup[$property])
+ || ( isset($inlineLayoutPropertyLookup[$property]) && ! isset($forcedPropertyLookup[$property]) )
+ ) {
+ // Preserve source inline layout and list markers over a later
+ // plain author class without introducing !important.
+ $normalPriorityDeclarations[] = $property . ':' . $value;
continue;
}
@@ -418,11 +433,11 @@ private function inlineGeometryClassName(
// rules retain their normal cascade priority through specificity.
$importantDeclarations[] = $property . ':' . $value . ' !important';
}
- $signature = implode(';', array_merge($layoutDeclarations, $importantDeclarations));
+ $signature = implode(';', array_merge($normalPriorityDeclarations, $importantDeclarations));
$className = ($this->geometryCarrierClassAllocator ??= new GeometryCarrierClassAllocator())->allocate($this->geometryStructuralPath($element) . "\n" . $signature);
$rules = array();
- if ( array() !== $layoutDeclarations ) {
- $rules[] = ':root .' . $className . '{' . implode(';', $layoutDeclarations) . '}';
+ if ( array() !== $normalPriorityDeclarations ) {
+ $rules[] = ':root .' . $className . '{' . implode(';', $normalPriorityDeclarations) . '}';
}
if ( array() !== $importantDeclarations ) {
$rules[] = '.' . $className . '{' . implode(';', $importantDeclarations) . '}';
@@ -1758,7 +1773,7 @@ private function cssDeclarations(string $style): array
[$name, $value] = array_map('trim', explode(':', $declaration, 2));
$name = strtolower($name);
$value = preg_replace('/\s+/', ' ', $value) ?? $value;
- $allowsImageUrl = in_array($name, array( 'background', 'background-image' ), true) && ! preg_match('/(?:expression\s*\(|javascript\s*:)/i', $value);
+ $allowsImageUrl = in_array($name, array( 'background', 'background-image', 'list-style', 'list-style-image' ), true) && ! preg_match('/(?:expression\s*\(|javascript\s*:)/i', $value);
if ( '' !== $name && '' !== $value && ( $allowsImageUrl || ! preg_match('/(?:expression\s*\(|javascript\s*:|url\s*\()/i', $value) ) ) {
$declarations[$name] = $value;
}
diff --git a/php-transformer/tests/unit/block-style-support-conversion.php b/php-transformer/tests/unit/block-style-support-conversion.php
index 35df784b..57d91d02 100644
--- a/php-transformer/tests/unit/block-style-support-conversion.php
+++ b/php-transformer/tests/unit/block-style-support-conversion.php
@@ -90,6 +90,38 @@
$assert(! str_contains($nativeGridMarkup, 'gap:1.2rem'), '16c: native Group grid markup omits inline gap that Gutenberg save does not reproduce', $nativeGridMarkup);
$assert(str_contains($nativeGridCss, 'gap:1.2rem !important'), '16d: inline native Group grid gap moves to the generated geometry carrier', $nativeGridCss);
+$unorderedListSource = '';
+$unorderedListResult = ( new HtmlTransformer() )->transform($unorderedListSource)->toArray();
+$unorderedListAttrs = $unorderedListResult['blocks'][0]['attrs'] ?? array();
+$unorderedListCss = implode("\n", array_map(static fn (array $asset): string => (string) ($asset['content'] ?? ''), is_array($unorderedListResult['assets'] ?? null) ? $unorderedListResult['assets'] : array()));
+$assert(str_contains($unorderedListSource, 'list-style:none'), 'L1 precondition: unordered list fixture authors list-style:none', $unorderedListSource);
+$assert(str_contains((string) ($unorderedListAttrs['className'] ?? ''), 'be-inline-geometry-') && str_contains($unorderedListCss, 'list-style:none') && ! str_contains($unorderedListCss, 'list-style:none !important'), 'L1: unordered list carries authored list-style:none without !important', $unorderedListCss);
+
+$orderedListResult = ( new HtmlTransformer() )->transform('- First
')->toArray();
+$orderedListAttrs = $orderedListResult['blocks'][0]['attrs'] ?? array();
+$orderedListCss = implode("\n", array_map(static fn (array $asset): string => (string) ($asset['content'] ?? ''), is_array($orderedListResult['assets'] ?? null) ? $orderedListResult['assets'] : array()));
+$assert(true === ($orderedListAttrs['ordered'] ?? false) && str_contains((string) ($orderedListAttrs['className'] ?? ''), 'be-inline-geometry-') && str_contains($orderedListCss, 'list-style:none') && ! str_contains($orderedListCss, 'list-style:none !important'), 'L2: ordered list carries authored list-style:none without !important', $orderedListCss);
+
+$plainListResult = ( new HtmlTransformer() )->transform('')->toArray();
+$plainListMarkup = (string) ($plainListResult['serialized_blocks'] ?? '');
+$assert(
+ '' === $plainListMarkup
+ && array() === ($plainListResult['assets'] ?? array()),
+ 'L3: a list without authored list-style keeps canonical marker-rendering output and gains no carrier',
+ $plainListMarkup
+);
+
+$listLonghandsResult = ( new HtmlTransformer() )->transform('')->toArray();
+$listLonghandsCss = implode("\n", array_map(static fn (array $asset): string => (string) ($asset['content'] ?? ''), is_array($listLonghandsResult['assets'] ?? null) ? $listLonghandsResult['assets'] : array()));
+$assert(
+ str_contains($listLonghandsCss, 'list-style-type:square')
+ && str_contains($listLonghandsCss, 'list-style-position:inside')
+ && str_contains($listLonghandsCss, 'list-style-image:url(https://example.com/marker.svg)')
+ && ! str_contains($listLonghandsCss, '!important'),
+ 'list-style longhands ride the generated carrier without !important',
+ $listLonghandsCss
+);
+
$cardHtml = '';
$cardResult = ( new HtmlTransformer() )->transform($cardHtml, array())->toArray();
$cardShell = $cardResult['blocks'][0] ?? array();