Unverified Commit 40d6aca4 authored by lauriii's avatar lauriii

Issue #2994909 by bnjmnm, tedbow, kostyashupenko, tim.plunkett, lauriii,...

Issue #2994909 by bnjmnm, tedbow, kostyashupenko, tim.plunkett, lauriii, samuel.mortenson, Kristen Pol, xjm, andrewmacpherson, alwaysworking, DyanneNova, jrockowitz, worldlinemine, phenaproxima, ckrina, benjifisher, rainbreaw, webchick: Highlight active element while working with dialogs in Layout Builder
parent ba442448
...@@ -68,6 +68,7 @@ ...@@ -68,6 +68,7 @@
border-radius: 26px; border-radius: 26px;
margin-left: -10px; margin-left: -10px;
margin-right: 6px; margin-right: 6px;
z-index: 2;
} }
.layout-builder__link--remove:hover { .layout-builder__link--remove:hover {
...@@ -121,3 +122,22 @@ ...@@ -121,3 +122,22 @@
display: block; display: block;
padding: 15px 0 15px 25px; padding: 15px 0 15px 25px;
} }
.layout-builder__add-section.is-layout-builder-highlighted {
margin-bottom: calc(1.5em - 8px);
outline: none;
}
.layout-builder__layout.is-layout-builder-highlighted,
.layout-builder-block.is-layout-builder-highlighted,
.layout-builder__add-block.is-layout-builder-highlighted {
margin: -4px -2px;
position: relative;
z-index: 1;
}
.layout-builder__add-block.is-layout-builder-highlighted,
.layout-builder__add-section.is-layout-builder-highlighted,
.layout-builder__layout.is-layout-builder-highlighted:before,
.layout-builder__layout.is-layout-builder-highlighted,
.layout-builder-block.is-layout-builder-highlighted {
border: 4px solid #000;
}
...@@ -201,4 +201,86 @@ ...@@ -201,4 +201,86 @@
.attr('tabindex', -1); .attr('tabindex', -1);
}, },
}; };
// After a dialog opens, highlight element that the dialog is acting on.
$(window).on('dialog:aftercreate', (event, dialog, $element) => {
if (Drupal.offCanvas.isOffCanvas($element)) {
// Start by removing any existing highlighted elements.
$('.is-layout-builder-highlighted').removeClass(
'is-layout-builder-highlighted',
);
/*
* Every dialog has a single 'data-layout-builder-target-highlight-id'
* attribute. Every dialog-opening element has a unique
* 'data-layout-builder-highlight-id' attribute.
*
* When the value of data-layout-builder-target-highlight-id matches
* an element's value of data-layout-builder-highlight-id, the class
* 'is-layout-builder-highlighted' is added to element.
*/
const id = $element
.find('[data-layout-builder-target-highlight-id]')
.attr('data-layout-builder-target-highlight-id');
if (id) {
$(`[data-layout-builder-highlight-id="${id}"]`).addClass(
'is-layout-builder-highlighted',
);
}
}
});
/*
* When a Layout Builder dialog is triggered, the main canvas resizes. After
* the resize transition is complete, see if the target element is still
* visible in viewport. If not, scroll page so the target element is again
* visible.
*
* @todo Replace this custom solution when a general solution is made
* available with https://www.drupal.org/node/3033410
*/
if (document.querySelector('[data-off-canvas-main-canvas]')) {
const mainCanvas = document.querySelector('[data-off-canvas-main-canvas]');
// This event fires when canvas CSS transitions are complete.
mainCanvas.addEventListener('transitionend', () => {
const $target = $('.is-layout-builder-highlighted');
if ($target.length > 0) {
// These four variables are used to determine if the element is in the
// viewport.
const targetTop = $target.offset().top;
const targetBottom = targetTop + $target.outerHeight();
const viewportTop = $(window).scrollTop();
const viewportBottom = viewportTop + $(window).height();
// If the element is not in the viewport, scroll it into view.
if (targetBottom < viewportTop || targetTop > viewportBottom) {
const viewportMiddle = (viewportBottom + viewportTop) / 2;
const scrollAmount = targetTop - viewportMiddle;
// Check whether the browser supports scrollBy(options). If it does
// not, use scrollBy(x-coord, y-coord) instead.
if ('scrollBehavior' in document.documentElement.style) {
window.scrollBy({
top: scrollAmount,
left: 0,
behavior: 'smooth',
});
} else {
window.scrollBy(0, scrollAmount);
}
}
}
});
}
// When a dialog closes, remove the highlight from all elements.
$(window).on('dialog:afterclose', (event, dialog, $element) => {
if (Drupal.offCanvas.isOffCanvas($element)) {
$('.is-layout-builder-highlighted').removeClass(
'is-layout-builder-highlighted',
);
}
});
})(jQuery, Drupal); })(jQuery, Drupal);
...@@ -93,4 +93,51 @@ ...@@ -93,4 +93,51 @@
}).attr('tabindex', -1); }).attr('tabindex', -1);
} }
}; };
$(window).on('dialog:aftercreate', function (event, dialog, $element) {
if (Drupal.offCanvas.isOffCanvas($element)) {
$('.is-layout-builder-highlighted').removeClass('is-layout-builder-highlighted');
var id = $element.find('[data-layout-builder-target-highlight-id]').attr('data-layout-builder-target-highlight-id');
if (id) {
$('[data-layout-builder-highlight-id="' + id + '"]').addClass('is-layout-builder-highlighted');
}
}
});
if (document.querySelector('[data-off-canvas-main-canvas]')) {
var mainCanvas = document.querySelector('[data-off-canvas-main-canvas]');
mainCanvas.addEventListener('transitionend', function () {
var $target = $('.is-layout-builder-highlighted');
if ($target.length > 0) {
var targetTop = $target.offset().top;
var targetBottom = targetTop + $target.outerHeight();
var viewportTop = $(window).scrollTop();
var viewportBottom = viewportTop + $(window).height();
if (targetBottom < viewportTop || targetTop > viewportBottom) {
var viewportMiddle = (viewportBottom + viewportTop) / 2;
var scrollAmount = targetTop - viewportMiddle;
if ('scrollBehavior' in document.documentElement.style) {
window.scrollBy({
top: scrollAmount,
left: 0,
behavior: 'smooth'
});
} else {
window.scrollBy(0, scrollAmount);
}
}
}
});
}
$(window).on('dialog:afterclose', function (event, dialog, $element) {
if (Drupal.offCanvas.isOffCanvas($element)) {
$('.is-layout-builder-highlighted').removeClass('is-layout-builder-highlighted');
}
});
})(jQuery, Drupal); })(jQuery, Drupal);
\ No newline at end of file
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\layout_builder\Context\LayoutBuilderContextTrait; use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
use Drupal\layout_builder\SectionStorageInterface; use Drupal\layout_builder\SectionStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
...@@ -21,6 +22,7 @@ class ChooseBlockController implements ContainerInjectionInterface { ...@@ -21,6 +22,7 @@ class ChooseBlockController implements ContainerInjectionInterface {
use AjaxHelperTrait; use AjaxHelperTrait;
use LayoutBuilderContextTrait; use LayoutBuilderContextTrait;
use LayoutBuilderHighlightTrait;
use StringTranslationTrait; use StringTranslationTrait;
/** /**
...@@ -124,6 +126,7 @@ public function build(SectionStorageInterface $section_storage, $delta, $region) ...@@ -124,6 +126,7 @@ public function build(SectionStorageInterface $section_storage, $delta, $region)
$block_categories['#type'] = 'container'; $block_categories['#type'] = 'container';
$block_categories['#attributes']['class'][] = 'block-categories'; $block_categories['#attributes']['class'][] = 'block-categories';
$block_categories['#attributes']['class'][] = 'js-layout-builder-categories'; $block_categories['#attributes']['class'][] = 'js-layout-builder-categories';
$block_categories['#attributes']['data-layout-builder-target-highlight-id'] = $this->blockAddHighlightId($delta, $region);
// @todo Explicitly cast delta to an integer, remove this in // @todo Explicitly cast delta to an integer, remove this in
// https://www.drupal.org/project/drupal/issues/2984509. // https://www.drupal.org/project/drupal/issues/2984509.
...@@ -188,6 +191,7 @@ public function inlineBlockList(SectionStorageInterface $section_storage, $delta ...@@ -188,6 +191,7 @@ public function inlineBlockList(SectionStorageInterface $section_storage, $delta
'#attributes' => $this->getAjaxAttributes(), '#attributes' => $this->getAjaxAttributes(),
]; ];
} }
$build['links']['#attributes']['data-layout-builder-target-highlight-id'] = $this->blockAddHighlightId($delta, $region);
return $build; return $build;
} }
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
use Drupal\layout_builder\SectionStorageInterface; use Drupal\layout_builder\SectionStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
...@@ -19,6 +20,7 @@ ...@@ -19,6 +20,7 @@
class ChooseSectionController implements ContainerInjectionInterface { class ChooseSectionController implements ContainerInjectionInterface {
use AjaxHelperTrait; use AjaxHelperTrait;
use LayoutBuilderHighlightTrait;
use StringTranslationTrait; use StringTranslationTrait;
/** /**
...@@ -96,6 +98,7 @@ public function build(SectionStorageInterface $section_storage, $delta) { ...@@ -96,6 +98,7 @@ public function build(SectionStorageInterface $section_storage, $delta) {
'class' => [ 'class' => [
'layout-selection', 'layout-selection',
], ],
'data-layout-builder-target-highlight-id' => $this->sectionAddHighlightId($delta),
], ],
]; ];
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
use Drupal\Core\Render\Element\RenderElement; use Drupal\Core\Render\Element\RenderElement;
use Drupal\Core\Url; use Drupal\Core\Url;
use Drupal\layout_builder\Context\LayoutBuilderContextTrait; use Drupal\layout_builder\Context\LayoutBuilderContextTrait;
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface; use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Drupal\layout_builder\OverridesSectionStorageInterface; use Drupal\layout_builder\OverridesSectionStorageInterface;
use Drupal\layout_builder\SectionStorageInterface; use Drupal\layout_builder\SectionStorageInterface;
...@@ -24,6 +25,7 @@ class LayoutBuilder extends RenderElement implements ContainerFactoryPluginInter ...@@ -24,6 +25,7 @@ class LayoutBuilder extends RenderElement implements ContainerFactoryPluginInter
use AjaxHelperTrait; use AjaxHelperTrait;
use LayoutBuilderContextTrait; use LayoutBuilderContextTrait;
use LayoutBuilderHighlightTrait;
/** /**
* The layout tempstore repository. * The layout tempstore repository.
...@@ -212,6 +214,7 @@ protected function buildAddSectionLink(SectionStorageInterface $section_storage, ...@@ -212,6 +214,7 @@ protected function buildAddSectionLink(SectionStorageInterface $section_storage,
'#type' => 'container', '#type' => 'container',
'#attributes' => [ '#attributes' => [
'class' => ['layout-builder__add-section'], 'class' => ['layout-builder__add-section'],
'data-layout-builder-highlight-id' => $this->sectionAddHighlightId($delta),
], ],
]; ];
} }
...@@ -242,6 +245,7 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s ...@@ -242,6 +245,7 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
foreach (Element::children($build[$region]) as $uuid) { foreach (Element::children($build[$region]) as $uuid) {
$build[$region][$uuid]['#attributes']['class'][] = 'draggable'; $build[$region][$uuid]['#attributes']['class'][] = 'draggable';
$build[$region][$uuid]['#attributes']['data-layout-block-uuid'] = $uuid; $build[$region][$uuid]['#attributes']['data-layout-block-uuid'] = $uuid;
$build[$region][$uuid]['#attributes']['data-layout-builder-highlight-id'] = $this->blockUpdateHighlightId($uuid);
$build[$region][$uuid]['#contextual_links'] = [ $build[$region][$uuid]['#contextual_links'] = [
'layout_builder_block' => [ 'layout_builder_block' => [
'route_parameters' => [ 'route_parameters' => [
...@@ -281,7 +285,10 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s ...@@ -281,7 +285,10 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
), ),
]; ];
$build[$region]['layout_builder_add_block']['#type'] = 'container'; $build[$region]['layout_builder_add_block']['#type'] = 'container';
$build[$region]['layout_builder_add_block']['#attributes'] = ['class' => ['layout-builder__add-block']]; $build[$region]['layout_builder_add_block']['#attributes'] = [
'class' => ['layout-builder__add-block'],
'data-layout-builder-highlight-id' => $this->blockAddHighlightId($delta, $region),
];
$build[$region]['layout_builder_add_block']['#weight'] = 1000; $build[$region]['layout_builder_add_block']['#weight'] = 1000;
$build[$region]['#attributes']['data-region'] = $region; $build[$region]['#attributes']['data-region'] = $region;
$build[$region]['#attributes']['class'][] = 'layout-builder__region'; $build[$region]['#attributes']['class'][] = 'layout-builder__region';
...@@ -296,8 +303,10 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s ...@@ -296,8 +303,10 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s
'section_storage_type' => $storage_type, 'section_storage_type' => $storage_type,
'section_storage' => $storage_id, 'section_storage' => $storage_id,
])->toString(); ])->toString();
$build['#attributes']['data-layout-delta'] = $delta; $build['#attributes']['data-layout-delta'] = $delta;
$build['#attributes']['class'][] = 'layout-builder__layout'; $build['#attributes']['class'][] = 'layout-builder__layout';
$build['#attributes']['data-layout-builder-highlight-id'] = $this->sectionUpdateHighlightId($delta);
return [ return [
'#type' => 'container', '#type' => 'container',
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace Drupal\layout_builder\Form; namespace Drupal\layout_builder\Form;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
use Drupal\layout_builder\SectionComponent; use Drupal\layout_builder\SectionComponent;
use Drupal\layout_builder\SectionStorageInterface; use Drupal\layout_builder\SectionStorageInterface;
...@@ -13,6 +14,8 @@ ...@@ -13,6 +14,8 @@
*/ */
class AddBlockForm extends ConfigureBlockFormBase { class AddBlockForm extends ConfigureBlockFormBase {
use LayoutBuilderHighlightTrait;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -53,6 +56,7 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt ...@@ -53,6 +56,7 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
$section_storage->getSection($delta)->appendComponent($component); $section_storage->getSection($delta)->appendComponent($component);
$form_state->set('layout_builder__component', $component); $form_state->set('layout_builder__component', $component);
} }
$form['#attributes']['data-layout-builder-target-highlight-id'] = $this->blockAddHighlightId($delta, $region);
return $this->doBuildForm($form, $form_state, $section_storage, $delta, $component); return $this->doBuildForm($form, $form_state, $section_storage, $delta, $component);
} }
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
use Drupal\Core\Plugin\PluginFormInterface; use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Plugin\PluginWithFormsInterface; use Drupal\Core\Plugin\PluginWithFormsInterface;
use Drupal\layout_builder\Controller\LayoutRebuildTrait; use Drupal\layout_builder\Controller\LayoutRebuildTrait;
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface; use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Drupal\layout_builder\Section; use Drupal\layout_builder\Section;
use Drupal\layout_builder\SectionStorageInterface; use Drupal\layout_builder\SectionStorageInterface;
...@@ -24,6 +25,7 @@ ...@@ -24,6 +25,7 @@
class ConfigureSectionForm extends FormBase { class ConfigureSectionForm extends FormBase {
use AjaxFormHelperTrait; use AjaxFormHelperTrait;
use LayoutBuilderHighlightTrait;
use LayoutRebuildTrait; use LayoutRebuildTrait;
/** /**
...@@ -127,6 +129,8 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt ...@@ -127,6 +129,8 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
if ($this->isAjax()) { if ($this->isAjax()) {
$form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit'; $form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit';
} }
$target_highlight_id = $this->isUpdate ? $this->sectionUpdateHighlightId($delta) : $this->sectionAddHighlightId($delta);
$form['#attributes']['data-layout-builder-target-highlight-id'] = $target_highlight_id;
return $form; return $form;
} }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
use Drupal\Core\Form\ConfirmFormBase; use Drupal\Core\Form\ConfirmFormBase;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\layout_builder\Controller\LayoutRebuildTrait; use Drupal\layout_builder\Controller\LayoutRebuildTrait;
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
use Drupal\layout_builder\LayoutTempstoreRepositoryInterface; use Drupal\layout_builder\LayoutTempstoreRepositoryInterface;
use Drupal\layout_builder\SectionStorageInterface; use Drupal\layout_builder\SectionStorageInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
...@@ -18,6 +19,7 @@ ...@@ -18,6 +19,7 @@
abstract class LayoutRebuildConfirmFormBase extends ConfirmFormBase { abstract class LayoutRebuildConfirmFormBase extends ConfirmFormBase {
use AjaxFormHelperTrait; use AjaxFormHelperTrait;
use LayoutBuilderHighlightTrait;
use LayoutRebuildTrait; use LayoutRebuildTrait;
/** /**
...@@ -79,6 +81,8 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt ...@@ -79,6 +81,8 @@ public function buildForm(array $form, FormStateInterface $form_state, SectionSt
if ($this->isAjax()) { if ($this->isAjax()) {
$form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit'; $form['actions']['submit']['#ajax']['callback'] = '::ajaxSubmit';
$form['actions']['cancel']['#attributes']['class'][] = 'dialog-cancel'; $form['actions']['cancel']['#attributes']['class'][] = 'dialog-cancel';
$target_highlight_id = !empty($this->uuid) ? $this->blockUpdateHighlightId($this->uuid) : $this->sectionUpdateHighlightId($delta);
$form['#attributes']['data-layout-builder-target-highlight-id'] = $target_highlight_id;
} }
return $form; return $form;
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace Drupal\layout_builder\Form; namespace Drupal\layout_builder\Form;
use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Form\FormStateInterface;
use Drupal\layout_builder\LayoutBuilderHighlightTrait;
use Drupal\layout_builder\SectionStorageInterface; use Drupal\layout_builder\SectionStorageInterface;
/** /**
...@@ -12,6 +13,8 @@ ...@@ -12,6 +13,8 @@
*/ */
class UpdateBlockForm extends ConfigureBlockFormBase { class UpdateBlockForm extends ConfigureBlockFormBase {
use LayoutBuilderHighlightTrait;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -40,6 +43,7 @@ public function getFormId() { ...@@ -40,6 +43,7 @@ public function getFormId() {
*/ */
public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL, $delta = NULL, $region = NULL, $uuid = NULL) { public function buildForm(array $form, FormStateInterface $form_state, SectionStorageInterface $section_storage = NULL, $delta = NULL, $region = NULL, $uuid = NULL) {
$component = $section_storage->getSection($delta)->getComponent($uuid); $component = $section_storage->getSection($delta)->getComponent($uuid);
$form['#attributes']['data-layout-builder-target-highlight-id'] = $this->blockUpdateHighlightId($uuid);
return $this->doBuildForm($form, $form_state, $section_storage, $delta, $component); return $this->doBuildForm($form, $form_state, $section_storage, $delta, $component);
} }
......
<?php
namespace Drupal\layout_builder;
/**
* A trait for generating IDs used to highlight active UI elements.
*/
trait LayoutBuilderHighlightTrait {
/**
* Provides the ID used to highlight the active Layout Builder UI element.
*
* @param string $delta
* The section the block is in.
* @param string $region
* The section region in which the block is placed.
*
* @return string
* The highlight ID of the block.
*/
protected function blockAddHighlightId($delta, $region) {
return "block-$delta-$region";
}
/**
* Provides the ID used to highlight the active Layout Builder UI element.
*
* @param string $uuid
* The uuid of the block.
*
* @return string
* The highlight ID of the block.
*/
protected function blockUpdateHighlightId($uuid) {
return $uuid;
}
/**
* Provides the ID used to highlight the active Layout Builder UI element.
*
* @param string $delta
* The location of the section.
*
* @return string
* The highlight ID of the section.
*/
protected function sectionAddHighlightId($delta) {
return "section-$delta";
}
/**
* Provides the ID used to highlight the active Layout Builder UI element.
*
* @param string $delta
* The location of the section.
*
* @return string
* The highlight ID of the section.
*/
protected function sectionUpdateHighlightId($delta) {
return "section-update-$delta";
}
}
...@@ -231,19 +231,20 @@ protected function assertContextualLinksClickable() { ...@@ -231,19 +231,20 @@ protected function assertContextualLinksClickable() {
protected function assertContextualLinkRetainsMouseup() { protected function assertContextualLinkRetainsMouseup() {
$assert_session = $this->assertSession(); $assert_session = $this->assertSession();
$page = $this->getSession()->getPage(); $page = $this->getSession()->getPage();
$body_field_selector = '.block-field-blocknodebundle-with-section-fieldbody';
$body_block = $page->find('css', '.block-field-blocknodebundle-with-section-fieldbody'); $body_block = $page->find('css', $body_field_selector);
$this->assertNotEmpty($body_block); $this->assertNotEmpty($body_block);
// Get the current Y position of the body block. // Get the current Y position of the body block.
$body_block_y_position = $this->getSession()->evaluateScript("document.getElementsByClassName('block-field-blocknodebundle-with-section-fieldbody')[0].getBoundingClientRect().top + window.pageYOffset"); $body_block_top_position = $this->getElementVerticalPosition($body_field_selector, 'top');
$body_block_contextual_link_button = $body_block->find('css', '.trigger'); $body_block_contextual_link_button = $body_block->find('css', '.trigger');
$this->assertNotEmpty($body_block_contextual_link_button); $this->assertNotEmpty($body_block_contextual_link_button);
// If the body block contextual link is hidden, make it visible. // If the body block contextual link is hidden, make it visible.
if ($body_block_contextual_link_button->hasClass('visually-hidden')) { if ($body_block_contextual_link_button->hasClass('visually-hidden')) {
$this->toggleContextualTriggerVisibility('.block-field-blocknodebundle-with-section-fieldbody'); $this->toggleContextualTriggerVisibility($body_field_selector);
} }
// For the purposes of this test, the contextual link must be accessed with // For the purposes of this test, the contextual link must be accessed with
...@@ -254,13 +255,41 @@ protected function assertContextualLinkRetainsMouseup() { ...@@ -254,13 +255,41 @@ protected function assertContextualLinkRetainsMouseup() {
$assert_session->assertWaitOnAjaxRequest(); $assert_session->assertWaitOnAjaxRequest();
// After the contextual link opens the dialog, move the mouse pointer // After the contextual link opens the dialog, move the mouse pointer
// elsewhere on the page. // elsewhere on the page. If mouse up were not working correctly this would
// actually drag the body field too.
$this->movePointerTo('#iframe-that-should-be-disabled'); $this->movePointerTo('#iframe-that-should-be-disabled');
// If mouseup is working properly, the body block should be in the same $new_body_block_bottom_position = $this->getElementVerticalPosition($body_field_selector, 'bottom');
// position it was when $body_block_y_position was declared. $iframe_top_position = $this->getElementVerticalPosition('#iframe-that-should-be-disabled', 'top');
$new_body_block_y_position = $this->getSession()->evaluateScript("document.getElementsByClassName('block-field-blocknodebundle-with-section-fieldbody')[0].getBoundingClientRect().top + window.pageYOffset");
$this->assertEquals($body_block_y_position, $new_body_block_y_position); $minimum_distance_mouse_moved = $iframe_top_position - $new_body_block_bottom_position;
$this->assertGreaterThan(200, $minimum_distance_mouse_moved, 'The mouse moved at least 200 pixels');
// If mouseup is working properly, the body block should be nearly in same
// position as it was when $body_block_y_position was declared. It will have
// moved slightly because the current block being configured will have a
// border that was not present when the dialog was not open.
$new_body_block_top_position = $this->getElementVerticalPosition($body_field_selector, 'top');
$distance_body_block_moved = abs($body_block_top_position - $new_body_block_top_position);
// Confirm that body moved only slightly compared to the distance the mouse
// moved and therefore was not dragged when the mouse moved.
$this->assertGreaterThan($distance_body_block_moved * 20, $minimum_distance_mouse_moved);
}
/**
* Gets the element position.
*
* @param string $css_selector
* The CSS selector of the element.
* @param string $position_type
* The position type to get, either 'top' or 'bottom'.
*
* @return int
* The element position.
*/
protected function getElementVerticalPosition($css_selector, $position_type) {
$this->assertTrue(in_array($position_type, ['top', 'bottom']), 'Expected position type.');
return (int) $this->getSession()->evaluateScript("document.querySelector('$css_selector').getBoundingClientRect().$position_type + window.pageYOffset");
} }
/** /**
......
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
namespace Drupal\Tests\layout_builder\FunctionalJavascript; namespace Drupal\Tests\layout_builder\FunctionalJavascript;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase; use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
/** /**
* Tests the Layout Builder UI. * Tests the Layout Builder UI.
...@@ -11,6 +13,8 @@ ...@@ -11,6 +13,8 @@
*/ */
class LayoutBuilderUiTest extends WebDriverTestBase { class LayoutBuilderUiTest extends WebDriverTestBase {
use ContextualLinkClickTrait;
/** /**
* Path prefix for the field UI for the test bundle. * Path prefix for the field UI for the test bundle.
* *
...@@ -22,6 +26,10 @@ class LayoutBuilderUiTest extends WebDriverTestBase { ...@@ -22,6 +26,10 @@ class LayoutBuilderUiTest extends WebDriverTestBase {
'layout_builder', 'layout_builder',
'block', 'block',
'node', 'node',
'block_content',