Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions php-transformer/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@
"php tests/unit/responsive-layered-block-margin.php",
"php tests/unit/responsive-counterpart-contracts.php",
"php tests/unit/captured-dialog-projector.php",
"php tests/unit/captured-dialog-source-panel-box.php",
"php tests/unit/captured-dialog-dropdown-panel.php",
"php tests/unit/scoped-mixed-dropdown.php",
"php tests/unit/capture-runtime-ancestor-parity.php",
Expand Down
28 changes: 26 additions & 2 deletions php-transformer/src/ArtifactCompiler/CapturedDialogProjector.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Generators\CapturedDialogBlockGenerator;
use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Patterns\NavigationPattern;
use Automattic\BlocksEngine\PhpTransformer\HtmlToBlocks\Support\SourceDom;
use Automattic\BlocksEngine\PhpTransformer\Support\EngineMarker;
use DOMDocument;
use DOMElement;
use DOMNode;
Expand Down Expand Up @@ -356,7 +357,7 @@ private function prepareGalleries(DOMDocument $document, array $states, string $
}

/**
* @param array{nodes:array<int, \DOMNode>, class:string, aria_label:string, aria_labelledby:string, aria_describedby:string, has_close_control:bool, self_positioned:bool} $fragment
* @param array{nodes:array<int, \DOMNode>, class:string, id:string, aria_label:string, aria_labelledby:string, aria_describedby:string, has_close_control:bool, self_positioned:bool} $fragment
* @param array<int, DOMElement> $triggers
* @param array<int, string> $triggerIds
*/
Expand All @@ -377,6 +378,16 @@ private function appendProjectedDialog(DOMDocument $document, array $fragment, a
$dialogElement->setAttribute('data-blocks-engine-captured-dialog', 'true');
$dialogElement->setAttribute('data-blocks-engine-triggers', implode(' ', $triggerIds));
if (is_string($fragment['class']) && '' !== $fragment['class']) $dialogElement->setAttribute('class', $fragment['class']);
// The dialog copies a source panel whose id another element of the
// document still holds (the capture's own hidden copy), so it cannot
// take that id. Author rules that name the panel by id (its width,
// offset, paint variables) would then reach only the hidden copy.
// Carry the id as the editor anchor class the author stylesheet
// projector already keeps such id selectors live through.
$sourceId = $fragment['id'];
if ('' !== $sourceId && $sourceId !== $dialogId && $this->documentHoldsId($document, $sourceId)) {
$dialogElement->setAttribute('class', trim($dialogElement->getAttribute('class') . ' ' . EngineMarker::editorAnchorClass($sourceId)));
}
// The producer observed whether the source panel was modal or a
// dropdown. A dropdown opens without trapping the page and keeps the
// source trigger as its toggle. Where it sits follows the evidence:
Expand Down Expand Up @@ -1100,7 +1111,7 @@ private function insideProjectedDialog(DOMElement $element): bool
return false;
}

/** @return array{nodes:array<int, \DOMNode>, class:string, aria_label:string, aria_labelledby:string, aria_describedby:string, has_close_control:bool, self_positioned:bool}|null */
/** @return array{nodes:array<int, \DOMNode>, class:string, id:string, aria_label:string, aria_labelledby:string, aria_describedby:string, has_close_control:bool, self_positioned:bool}|null */
private function safeDialogFragment(string $html): ?array
{
$previous = libxml_use_internal_errors(true);
Expand Down Expand Up @@ -1153,6 +1164,7 @@ private function safeDialogFragment(string $html): ?array
return array(
'nodes' => iterator_to_array($container->childNodes),
'class' => $sourceRoot instanceof DOMElement ? trim($sourceRoot->getAttribute('class')) : '',
'id' => $sourceRoot instanceof DOMElement ? SourceDom::safeAnchor($sourceRoot->getAttribute('id')) : '',
'aria_label' => $sourceRoot instanceof DOMElement ? trim($sourceRoot->getAttribute('aria-label')) : '',
'aria_labelledby' => $sourceRoot instanceof DOMElement ? trim($sourceRoot->getAttribute('aria-labelledby')) : '',
'aria_describedby' => $sourceRoot instanceof DOMElement ? trim($sourceRoot->getAttribute('aria-describedby')) : '',
Expand All @@ -1164,6 +1176,18 @@ private function safeDialogFragment(string $html): ?array
);
}

/**
* Whether a source element still holds the id. The capture's own panel
* envelope does not count: its id is the wiring key, not the source
* panel's identity, and no author rule names it.
*/
private function documentHoldsId(DOMDocument $document, string $id): bool
{
$matches = (new DOMXPath($document))->query('//*[@id=' . $this->xpathLiteral($id) . '][not(@' . CaptureEvidence::DIALOG_PANEL . ')]');

return $matches instanceof \DOMNodeList && 0 < $matches->length;
}

/** @param array<int, array<string, mixed>> $files @return array<string, mixed>|null */
private function jsonFile(array $files, string $path): ?array
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ final class CapturedDialogConverter implements ElementConverter
{
/**
* @param Closure(DOMElement, array<int, array<string, mixed>>&): array<int, array<string, mixed>> $convertChildren
* @param (Closure(DOMElement): string)|null $resolvedStyle The element's authored, cascade-resolved declarations.
*/
public function __construct(
private readonly HtmlTransformerSession $session,
private readonly Closure $convertChildren
private readonly Closure $convertChildren,
private readonly ?Closure $resolvedStyle = null
) {
}

Expand Down Expand Up @@ -54,6 +56,22 @@ private function isSupersededByNativeNavigation(DOMElement $dialog): bool
return false;
}

/**
* A modal whose source panel positions itself (`position:fixed` or
* `absolute` from its own author rules) drew its own box: the source panel
* was not a `<dialog>`, so it had no user agent padding, border, auto
* margins, insets or size caps. Mark it so the block stylesheet drops that
* geometry and the source rules alone place the panel.
*/
private function modalPlacement(DOMElement $element): string
{
if ( 'dialog' !== strtolower($element->tagName) || '' !== SourceDom::attr($element, 'data-blocks-engine-presentation') || null === $this->resolvedStyle ) {
return '';
}

return 1 === preg_match('/(?:^|;)\s*position\s*:\s*(?:fixed|absolute)\b/i', ($this->resolvedStyle)($element)) ? 'source' : '';
}

/**
* @param array<int, array<string, mixed>> $fallbacks
* @return array<string, mixed>
Expand All @@ -76,7 +94,7 @@ public function block(DOMElement $element, array &$fallbacks): array
'ariaDescribedby' => trim(SourceDom::attr($element, 'aria-describedby')),
'className' => trim(SourceDom::attr($element, 'class')),
'presentation' => 'dropdown' === SourceDom::attr($element, 'data-blocks-engine-presentation') ? 'dropdown' : '',
'placement' => in_array(SourceDom::attr($element, 'data-blocks-engine-placement'), array('in-place', 'under-header', 'source'), true) ? SourceDom::attr($element, 'data-blocks-engine-placement') : '',
'placement' => in_array(SourceDom::attr($element, 'data-blocks-engine-placement'), array('in-place', 'under-header', 'source'), true) ? SourceDom::attr($element, 'data-blocks-engine-placement') : $this->modalPlacement($element),
'addCloseButton' => 'true' === SourceDom::attr($element, 'data-blocks-engine-add-close'),
'gallerySelection' => json_decode(SourceDom::attr($element, 'data-blocks-engine-gallery-selection'), true) ?: array(),
'ancestorState' => CapturedDialogBlockGenerator::ancestorState(json_decode(SourceDom::attr($element, 'data-blocks-engine-ancestor-state'), true)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,10 +298,15 @@ function mountAll() { document.querySelectorAll( 'dialog[data-blocks-engine-trig
// rules have no specificity, so the source classes still win.
// A dropdown kept at its observed source place flows there like the
// source panel did: only the user agent's dialog box is reset.
// A modal whose source panel positions itself keeps the source's own
// placement: the user agent's box geometry (padding, border, auto
// margins, insets and size caps) is reset under its rules. Canvas
// paint stays as the backdrop for panel layers that paint over it.
$style = 'dialog[data-blocks-engine-triggers]:not([open]){display:none!important}'
. 'div[data-blocks-engine-presentation="dropdown"][data-blocks-engine-triggers]:not([open]){display:none!important}'
. ':where(dialog[data-blocks-engine-presentation="dropdown"][data-blocks-engine-placement="under-header"]){position:fixed;top:var(--blocks-engine-dropdown-top,0px);left:0;width:100%;max-width:none;max-height:calc(100vh - var(--blocks-engine-dropdown-top,0px));margin:0;overflow-y:auto;z-index:var(--blocks-engine-dropdown-layer,auto);background-color:var(--blocks-engine-dropdown-background,Canvas);-webkit-backdrop-filter:var(--blocks-engine-dropdown-backdrop,none);backdrop-filter:var(--blocks-engine-dropdown-backdrop,none);color:inherit}'
. ':where(dialog[data-blocks-engine-presentation="dropdown"][data-blocks-engine-placement="in-place"]){position:static;inset:auto;width:auto;height:auto;max-width:none;max-height:none;margin:0;padding:0;border-width:0;background-color:transparent;color:inherit;overflow:visible}';
. ':where(dialog[data-blocks-engine-presentation="dropdown"][data-blocks-engine-placement="in-place"]){position:static;inset:auto;width:auto;height:auto;max-width:none;max-height:none;margin:0;padding:0;border-width:0;background-color:transparent;color:inherit;overflow:visible}'
. ':where(dialog:not([data-blocks-engine-presentation])[data-blocks-engine-placement="source"]){inset:auto;max-width:none;max-height:none;margin:0;padding:0;border-width:0}';

return array(
'name' => self::LOCAL_NAME,
Expand Down
3 changes: 2 additions & 1 deletion php-transformer/src/HtmlToBlocks/HtmlCompilation.php
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,8 @@ function (DOMElement $element, array &$fallbacks): ?array {
$this->session,
function (DOMElement $element, array &$fallbacks) use ($convertChildren): array {
return $convertChildren($element, $fallbacks, true);
}
},
fn (DOMElement $element): string => $this->styleResolver->specificityResolvedPresentationStyle($element)
);
$this->wrapperCoalescer = new WrapperCoalescer(
$this->sourceElementClassifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ public static function identities(AuthorStyleAnalysis $source): array
foreach ($source->sourceElementIds() as $id) {
$id = (string) $id;
if ('' === SourceDom::safeAnchor($id)) continue;
// A projected captured dialog copies an element whose id stays on
// that hidden copy; the dialog carries the id as its anchor class.
if (array() !== $source->sourceElementsByClass(EngineMarker::editorAnchorClass($id))) { $ids[$id] = true; continue; }
$elements = $source->sourceElementsById($id);
if (count($elements) < 2) continue;
foreach ($elements as $element) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
{
"id": "0a9c1bea54021ba60093b14cc124cefca0e60616",
"entry": "ArtifactCompiler::compile",
"expected_sha256": "011a8609d40356a62eced84729322d578e5826a1df060ade41004e98c59aedc0",
"expected_sha256": "a4b5a631f9867987cb18ce03a825ad9cb56a018d2819a88de96527192e25e992",
"outcome": "output"
},
{
Expand Down Expand Up @@ -143,7 +143,7 @@
{
"id": "15082c2dc6a6f26471c6b1b16a41446c0c3ed22a",
"entry": "ArtifactCompiler::compile",
"expected_sha256": "2c55d1a448a18ca91b31ad842a0895c4873a7ea69aaee4590329d3fbd9703dbd",
"expected_sha256": "5caf57647fc1a20efcb6857446b24d39846d2a908f79ff7542c25238976482f5",
"outcome": "output"
},
{
Expand Down Expand Up @@ -245,7 +245,7 @@
{
"id": "21e5cdbf5ec5e6081029d84c0c34ccac47171153",
"entry": "ArtifactCompiler::compile",
"expected_sha256": "7bdc637e0ac9c24bca6331d62f4eb2ab299a59baf0701fc2be91b3afb70c537b",
"expected_sha256": "fb9786813c92f8ac79f6110c4a8b16cde5d7ca93ed1f60df258c7580dcfaa617",
"outcome": "output"
},
{
Expand Down Expand Up @@ -341,7 +341,7 @@
{
"id": "2915d21f717a45ad97cc901e84ead8debcb5e07b",
"entry": "ArtifactCompiler::compile",
"expected_sha256": "0f6cd685ea08990e95421490ebf96d0c302848d03650578c60693e3714d066d4",
"expected_sha256": "3bbd431f4618ff388971fd7062932e4ef9ec2736f34bc15677f7161567ba8b18",
"outcome": "output"
},
{
Expand Down Expand Up @@ -437,7 +437,7 @@
{
"id": "37284b0ef56612a5ff0cda0976e9baf91af033cb",
"entry": "ArtifactCompiler::compile",
"expected_sha256": "2103fa98642d8db5447e68d5a2642d27165f74f20e511e9120b958b23bf9bd0e",
"expected_sha256": "f1e057f69529822b43b9a6d8248f1571a80175c704ce8755ba8c2ab832e9bde1",
"outcome": "output"
},
{
Expand Down Expand Up @@ -653,7 +653,7 @@
{
"id": "55b724ac2c76d2fde1959aeee3370f76b3d8129d",
"entry": "ArtifactCompiler::compile",
"expected_sha256": "60ac73a9c8f5740223f04e57fc8702354a374ff301a3c969daea8538b0823d5a",
"expected_sha256": "3d168b155493f0b6bbb643e1bcfe25fe645b8b4ff1e285034798a7077b5af83f",
"outcome": "output"
},
{
Expand Down Expand Up @@ -1019,7 +1019,7 @@
{
"id": "7a7af8982960e28f4cf964953c85b58a510699e0",
"entry": "HtmlTransformer::transform",
"expected_sha256": "d7a21036360daea3b22782bbff2274b410450f5e3ec0342901d0c13691532b4f",
"expected_sha256": "ce5d6431831c016aad5641f3baf9bf9c703dfc848a4d73c1a02036ef14ce933c",
"outcome": "output"
},
{
Expand All @@ -1043,7 +1043,7 @@
{
"id": "7c2d1217f68f1eeed57e660c70936247d937c414",
"entry": "ArtifactCompiler::compile",
"expected_sha256": "aff5b81437d0f8acbd64bd33825fbd05d7e2d4c51213f1817df47d5a083f4a8a",
"expected_sha256": "82fbfff9b3531804790645d27f7588504498728ee69af0dea5f10426e977b1e1",
"outcome": "output"
},
{
Expand Down Expand Up @@ -1241,7 +1241,7 @@
{
"id": "927aa36875da93af28e278f37359f0c3fbbee7d8",
"entry": "ArtifactCompiler::compile",
"expected_sha256": "803cea008023246f4a4086946263d6cf62b3a79531740656e851f9a1917a01ca",
"expected_sha256": "1683437b9d2eb2289844a1fdece79f215cd4a80050558e6d8be39c826d3811c3",
"outcome": "output"
},
{
Expand All @@ -1259,7 +1259,7 @@
{
"id": "944dd63db631afc5664d39d5f49658f016e89e27",
"entry": "ArtifactCompiler::compile",
"expected_sha256": "5a2c5d37daa05c773bab41b6df4ddf59293242bc8ddda3ae04f23616b77959f3",
"expected_sha256": "6388768329781c3053f604325298a120e62e85d9d1e54e8dd1d1d1bf6504d0ad",
"outcome": "output"
},
{
Expand Down Expand Up @@ -1793,7 +1793,7 @@
{
"id": "df9c24a89732ac84a5fb55a4fd84cb89492722c3",
"entry": "ArtifactCompiler::compile",
"expected_sha256": "160ab577d01f47b915fd211a2db22cfd6c1d81eec701818206f92dd5483fa93c",
"expected_sha256": "0e64f64ffc6891e29c60d401a765f44e846ce4bece7c0e23db006ad2b3d1dfe0",
"outcome": "output"
},
{
Expand Down Expand Up @@ -1841,7 +1841,7 @@
{
"id": "e5ea57430e2ac12b29b8351684acc9fde5ba6431",
"entry": "ArtifactCompiler::compile",
"expected_sha256": "5738d685415c7655edcf23f8c7d9e6854120b630e27bb2d8031e6dd996473119",
"expected_sha256": "248d9110c659b2bc4572ef5ded089d7502d350081457cde10f3ae458e69fe5f3",
"outcome": "output"
},
{
Expand Down
Loading
Loading