diff --git a/php-transformer/src/HtmlToBlocks/BlockFactory.php b/php-transformer/src/HtmlToBlocks/BlockFactory.php
index 0d57dd23..ae1ed02b 100644
--- a/php-transformer/src/HtmlToBlocks/BlockFactory.php
+++ b/php-transformer/src/HtmlToBlocks/BlockFactory.php
@@ -64,9 +64,9 @@ private function normalizeAttrsForBlock(string $name, array $attrs): array
{
$attrs = $this->normalizeClassNameAttr($attrs);
- // RichText block save() does not reproduce dimensions.maxWidth. Inline
+ // These core save functions do not reproduce dimensions.maxWidth. Inline
// max-width is retained by the generated geometry carrier stylesheet.
- if ( in_array($name, array( 'core/group', 'core/columns', 'core/list-item', 'core/paragraph' ), true) ) {
+ if ( in_array($name, array( 'core/group', 'core/columns', 'core/list-item', 'core/paragraph', 'core/separator' ), true) ) {
unset($attrs['style']['dimensions']['maxWidth']);
if ( empty($attrs['style']['dimensions']) ) {
unset($attrs['style']['dimensions']);
@@ -76,6 +76,19 @@ private function normalizeAttrsForBlock(string $name, array $attrs): array
}
}
+ if ( 'core/separator' === $name ) {
+ unset($attrs['style']['spacing']['margin']['left'], $attrs['style']['spacing']['margin']['right']);
+ if ( empty($attrs['style']['spacing']['margin']) ) {
+ unset($attrs['style']['spacing']['margin']);
+ }
+ if ( empty($attrs['style']['spacing']) ) {
+ unset($attrs['style']['spacing']);
+ }
+ if ( empty($attrs['style']) ) {
+ unset($attrs['style']);
+ }
+ }
+
if ( in_array($name, array( 'core/buttons', 'core/column', 'core/columns', 'core/group', 'core/heading', 'core/list', 'core/list-item', 'core/paragraph' ), true) ) {
unset($attrs['style']['spacing']['blockGap']);
if ( empty($attrs['style']['spacing']) ) {
diff --git a/php-transformer/src/HtmlToBlocks/HtmlTransformer.php b/php-transformer/src/HtmlToBlocks/HtmlTransformer.php
index 8b98a28a..e967ae8e 100644
--- a/php-transformer/src/HtmlToBlocks/HtmlTransformer.php
+++ b/php-transformer/src/HtmlToBlocks/HtmlTransformer.php
@@ -2094,7 +2094,7 @@ private function convertElement(DOMElement $element, array &$fallbacks, bool $ca
}
if ( 'hr' === $tagName ) {
- return $this->createBlock('core/separator', $this->presentationAttributes($element), array(), $element);
+ return $this->createBlock('core/separator', $this->presentationAttributes($element, array(), array( 'margin-left', 'margin-right' )), array(), $element);
}
if ( 'br' === $tagName ) {
diff --git a/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php b/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php
index c245bccc..8b04041e 100644
--- a/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php
+++ b/php-transformer/src/HtmlToBlocks/Style/StyleResolutionTrait.php
@@ -108,9 +108,9 @@ private function highValueStyleBoundaryPolicy(): HighValueStyleBoundaryPolicy
*
* @return array
*/
- private function presentationAttributes(DOMElement $element, array $excludedGeometryProperties = array()): array
+ private function presentationAttributes(DOMElement $element, array $excludedGeometryProperties = array(), array $forcedGeometryProperties = array()): array
{
- $cacheKey = $this->presentationCacheKey($element) . ':' . implode(',', $excludedGeometryProperties);
+ $cacheKey = $this->presentationCacheKey($element) . ':' . implode(',', $excludedGeometryProperties) . ':' . implode(',', $forcedGeometryProperties);
if ( isset($this->presentationAttributesCache[$cacheKey]) ) {
return $this->presentationAttributesCache[$cacheKey];
}
@@ -120,12 +120,15 @@ private function presentationAttributes(DOMElement $element, array $excludedGeom
$this->presentationDeclarations($element)
);
$mapped = $this->styleAttributeMapper()->map($declarations);
+ $forcedGeometryDeclarations = array() === $forcedGeometryProperties
+ ? array()
+ : $this->cssDeclarations((string) ($this->styleAttributeMapper()->serialize($mapped['style'] ?? array())['style'] ?? ''));
$attrs = array_filter(array_merge($mapped['attrs'] ?? array(), array(
'anchor' => $this->safeAnchor($this->attr($element, 'id')),
'className' => $this->mergePresentationClassNames(
$this->promotedClassName($this->attr($element, 'class')),
- $this->inlineGeometryClassName($element, $excludedGeometryProperties)
+ $this->inlineGeometryClassName($element, $excludedGeometryProperties, $forcedGeometryProperties, $forcedGeometryDeclarations)
),
'inlineGeometryStyle' => $this->inlineGeometryStyle($element, $excludedGeometryProperties),
'style' => $mapped['style'],
@@ -230,15 +233,15 @@ private function hasConditionalStyleFamily(DOMElement $element, string $family):
* inline geometry in a generated stylesheet; class-owned declarations are
* already retained by author stylesheet materialization.
*/
- private function inlineGeometryClassName(DOMElement $element, array $excludedProperties = array()): string
+ private function inlineGeometryClassName(DOMElement $element, array $excludedProperties = array(), array $forcedProperties = array(), array $forcedDeclarations = array()): string
{
$declarations = $this->cssDeclarations($this->attr($element, 'style'));
$geometry = array();
- foreach ($this->inlineGeometryProperties() as $property) {
+ foreach (array_values(array_unique(array_merge($this->inlineGeometryProperties(), $forcedProperties))) as $property) {
if (in_array($property, $excludedProperties, true)) {
continue;
}
- $rawValue = trim((string) ($declarations[$property] ?? ''));
+ $rawValue = trim((string) ($declarations[$property] ?? ($forcedDeclarations[$property] ?? '')));
if (1 === preg_match('/\s*!important\s*$/i', $rawValue)) {
continue;
}
diff --git a/php-transformer/tests/contract/run.php b/php-transformer/tests/contract/run.php
index 0ef48f8d..4d67d2bf 100644
--- a/php-transformer/tests/contract/run.php
+++ b/php-transformer/tests/contract/run.php
@@ -738,6 +738,13 @@ public function match(DOMElement $element, PatternContext $context): ?array
$assert(1 === substr_count($separatorMarkup, 'has-css-opacity'), 'separator serialization emits has-css-opacity exactly once');
$assert(! str_contains($separatorMarkup, 'wp-block-separator divider wp-block-separator'), 'separator serialization does not duplicate generated classes');
+$boundedSeparator = ( new HtmlTransformer() )->transform('
')->toArray();
+$boundedSeparatorAttrs = $boundedSeparator['blocks'][0]['attrs'] ?? array();
+$boundedSeparatorMarkup = (string) ($boundedSeparator['serialized_blocks'] ?? '');
+$boundedSeparatorCss = implode("\n", array_map(static fn (array $asset): string => (string) ($asset['content'] ?? ''), $boundedSeparator['assets'] ?? array()));
+$assert(array('top' => '0', 'bottom' => '0') === ($boundedSeparatorAttrs['style']['spacing']['margin'] ?? null) && ! isset($boundedSeparatorAttrs['style']['dimensions']), 'separator keeps only spacing supported by its canonical core block attributes');
+$assert(! str_contains($boundedSeparatorMarkup, 'margin-left:auto') && ! str_contains($boundedSeparatorMarkup, 'max-width:var(--max)') && str_contains($boundedSeparatorCss, 'margin-left:auto !important') && str_contains($boundedSeparatorCss, 'margin-right:auto !important') && str_contains($boundedSeparatorCss, 'max-width:var(--max) !important'), 'separator moves unsupported horizontal and width geometry to its generated carrier stylesheet');
+
$customStateFindings = ( new CanonicalSaveShapeValidator() )->findings(array(array(
'blockName' => 'core/group',
'attrs' => array(),