Skip to content

Commit 86edfc5

Browse files
authored
Merge pull request #2348 from Automattic/fix/2339-fullsite-header-css
Preserve media-linked header geometry in shared site plans
2 parents ff14305 + 1dbda31 commit 86edfc5

3 files changed

Lines changed: 92 additions & 1 deletion

File tree

‎php-transformer/composer.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"php tests/contract/shared-navigation-entity.php",
7575
"php tests/contract/shared-chrome-stylesheet-scope.php",
7676
"php tests/contract/shared-chrome-authored-rules.php",
77+
"php tests/contract/shared-empty-data-carrier-geometry.php",
7778
"php tests/contract/generated-block-document-identity.php",
7879
"php tests/contract/shared-shell-entity-bindings.php",
7980
"php tests/contract/form-control-not-menu-toggle.php",

‎php-transformer/src/HtmlToBlocks/Style/AuthorSelectorSemanticPreparer.php‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,10 @@ private function discoverAuthorAttributePaths(array $authorSelectors, AuthorStyl
272272
'width', 'min-width', 'max-width', 'height', 'min-height', 'max-height',
273273
'margin', 'padding', 'flex', 'flex-basis', 'flex-grow', 'flex-shrink', 'grid', 'grid-area',
274274
)));
275-
if ( ! $hasBoxGeometry && 'img' !== strtolower($element->tagName) ) {
275+
if ( ! $hasBoxGeometry
276+
&& ! $this->selectorRuleDeclaresBoxGeometry($authorSelector['selector'], $authorStyles)
277+
&& 'img' !== strtolower($element->tagName)
278+
) {
276279
continue;
277280
}
278281
$path = $element->getNodePath() ?? '';
@@ -284,6 +287,26 @@ private function discoverAuthorAttributePaths(array $authorSelectors, AuthorStyl
284287
}
285288
}
286289

290+
private function selectorRuleDeclaresBoxGeometry(string $selector, AuthorStyleAnalysis $authorStyles): bool
291+
{
292+
$geometry = array_fill_keys(array(
293+
'display', 'position', 'inset', 'top', 'right', 'bottom', 'left',
294+
'width', 'min-width', 'max-width', 'height', 'min-height', 'max-height',
295+
'margin', 'padding', 'flex', 'flex-basis', 'flex-grow', 'flex-shrink', 'grid', 'grid-area',
296+
), true);
297+
foreach ($authorStyles->styleRules() as $rule) {
298+
foreach ($rule['selectors'] ?? array() as $candidate) {
299+
if ($selector !== ($candidate['selector'] ?? null)) {
300+
continue;
301+
}
302+
if (array_intersect_key($rule['declarations'] ?? array(), $geometry) !== array()) {
303+
return true;
304+
}
305+
}
306+
}
307+
return false;
308+
}
309+
287310
private function discoverNegatedDataAttributeState(string $selector, AuthorStyleAnalysis $authorStyles, AuthorSelectorProjectionState $projections): void
288311
{
289312
if ( 1 !== preg_match_all(
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
require dirname(__DIR__, 2) . '/vendor/autoload.php';
5+
6+
use Automattic\BlocksEngine\PhpTransformer\ArtifactCompiler\ArtifactCompiler;
7+
8+
$assert = static function (bool $condition, string $message): void {
9+
if (! $condition) {
10+
throw new RuntimeException($message);
11+
}
12+
};
13+
14+
$document = static fn (string $hero): string => '<!doctype html><html><head><link rel="stylesheet" href="assets/chrome-desktop.css" media="(min-width:601px)"><link rel="stylesheet" href="assets/chrome-phone.css" media="(max-width:600px)"></head><body><header class="site-header"><div data-mesh-id="shared-header-carrier"></div></header><main id="' . $hero . '"><h1>Hero</h1></main></body></html>';
15+
$artifact = (new ArtifactCompiler())->compile(array(
16+
'entrypoint' => 'index.html',
17+
'files' => array(
18+
'index.html' => $document('hero-home'),
19+
'about.html' => $document('hero-about'),
20+
'assets/chrome-desktop.css' => array(
21+
'path' => 'assets/chrome-desktop.css',
22+
'kind' => 'css',
23+
'media' => '(min-width:601px)',
24+
'content' => '[data-mesh-id="shared-header-carrier"]{min-height:102px}',
25+
),
26+
'assets/chrome-phone.css' => array(
27+
'path' => 'assets/chrome-phone.css',
28+
'kind' => 'css',
29+
'media' => '(max-width:600px)',
30+
'content' => '[data-mesh-id="shared-header-carrier"]{min-height:56px}',
31+
),
32+
),
33+
))->toArray();
34+
35+
$plan = $artifact['source_reports']['wordpress_site_plan'] ?? array();
36+
$assert(array() !== $plan, 'the artifact produces a WordPress site plan');
37+
$parts = array_values(array_filter($plan['template_parts'] ?? array(), static fn (array $part): bool => in_array($part['placement']['kind'] ?? '', array('shared_shell', 'inline_shared_shell'), true)));
38+
$assert(array() !== $parts, 'the identical header is extracted as a shared template part');
39+
$partMarkup = implode('', array_column($parts, 'canonical_block_markup'));
40+
$assert(str_contains($partMarkup, 'blocks-engine-empty-visual-group'), 'the shared header keeps an editable empty visual carrier');
41+
42+
$css = implode("\n", array_map(static fn (array $asset): string => (string) ($asset['content'] ?? ''), $plan['assets'] ?? array()));
43+
$assert((bool) preg_match('/blocks-engine-attribute-(?!state-)[a-f0-9-]+/', $partMarkup), 'the empty data-addressed carrier receives a stable generic marker');
44+
$markers = array();
45+
if (preg_match_all('/(blocks-engine-attribute-(?!state-)[a-f0-9-]+)/', $partMarkup, $matches)) {
46+
$markers = array_values(array_unique($matches[1]));
47+
}
48+
$marker = $markers[0] ?? '';
49+
$assert('' !== $marker && (bool) preg_match('/(?:where\()?\.' . preg_quote($marker, '/') . '[^{}]*\{min-height:102px\}/', $css), 'desktop carrier geometry projects onto the emitted marker');
50+
$desktopAssets = array_values(array_filter($plan['assets'] ?? array(), static fn (array $asset): bool => 'css' === ($asset['kind'] ?? null) && '(min-width:601px)' === ($asset['media'] ?? null) && array(array('kind' => 'global')) === ($asset['scopes'] ?? array())));
51+
$assert(1 === count($desktopAssets) && str_contains((string) ($desktopAssets[0]['content'] ?? ''), 'min-height:102px'), 'desktop carrier geometry remains globally scoped for tablet and desktop widths');
52+
$phoneAssets = array_values(array_filter($plan['assets'] ?? array(), static fn (array $asset): bool => 'css' === ($asset['kind'] ?? null) && '(max-width:600px)' === ($asset['media'] ?? null) && array(array('kind' => 'global')) === ($asset['scopes'] ?? array())));
53+
$assert(1 === count($phoneAssets) && array_filter($markers, static fn (string $candidate): bool => str_contains((string) ($phoneAssets[0]['content'] ?? ''), '.' . $candidate)) && str_contains((string) ($phoneAssets[0]['content'] ?? ''), 'min-height:56px'), 'phone carrier geometry keeps its media scope on the emitted marker');
54+
$viewportHeights = array();
55+
foreach (array(390, 768, 1440) as $viewport) {
56+
$asset = $viewport <= 600 ? $phoneAssets[0] : $desktopAssets[0];
57+
$viewportHeights[$viewport] = str_contains((string) ($asset['content'] ?? ''), 'min-height:' . ($viewport <= 600 ? '56px' : '102px'))
58+
? ($viewport <= 600 ? 56 : 102)
59+
: 0;
60+
}
61+
$assert(array(390 => 56, 768 => 102, 1440 => 102) === $viewportHeights, 'header geometry contract covers phone, tablet, and desktop reference widths');
62+
$assert(! str_contains($css, '[data-mesh-id="shared-header-carrier"]'), 'the emitted stylesheet does not retain a dead source-only selector');
63+
$frontPage = array_values(array_filter($plan['templates'] ?? array(), static fn (array $candidate): bool => 'front-page' === ($candidate['slug'] ?? null)));
64+
$template = (string) ($frontPage[0]['canonical_block_markup'] ?? '');
65+
$assert(strpos($template, '"slug":"header"') < strpos($template, 'wp:post-content'), 'the emitted template places the shared header before the hero content');
66+
67+
echo "Shared empty data carrier geometry contract passed\n";

0 commit comments

Comments
 (0)