Issue #3274937 by nod_, woldtwerk, Dom., Wim Leers: Get CKEditor 5 to work in (modal) dialogs

merge-requests/2367/head
Lauri Eskola 2022-06-09 17:38:23 +03:00
parent f90d892297
commit 6d6ab58776
No known key found for this signature in database
GPG Key ID: 382FC0F5B0DF53F8
7 changed files with 192 additions and 0 deletions

View File

@ -440,6 +440,15 @@ function ckeditor5_library_info_alter(&$libraries, $extension) {
}
}
if ($extension === 'core') {
// CSS rule to resolve the conflict with z-index between CKEditor 5 and jQuery UI.
$libraries['drupal.dialog']['css']['component']['modules/ckeditor5/css/ckeditor5.dialog.fix.css'] = [];
// Fix the CKEditor 5 focus management in dialogs. Modify the library
// declaration to ensure this file is always loaded after
// drupal.dialog.jquery-ui.js.
$libraries['drupal.dialog']['js']['modules/ckeditor5/js/ckeditor5.dialog.fix.js'] = [];
}
// Only add translation processing if the locale module is enabled.
if (!$moduleHandler->moduleExists('locale')) {
return;

View File

@ -0,0 +1,3 @@
.ui-dialog ~ .ck-body-wrapper {
--ck-z-modal: 1261;
}

View File

@ -0,0 +1,29 @@
/**
* @file
* This file overrides the way jQuery UI focus trap works.
*
* When a focus event is fired while a CKEditor 5 instance is focused, do not
* trap the focus and let CKEditor 5 manage that focus.
*/
(($) => {
// Get core version of the _focusTabbable method.
const oldFocusTabbable = $.ui.dialog._proto._focusTabbable;
$.widget('ui.dialog', $.ui.dialog, {
// Override core override of jQuery UI's `_focusTabbable()` so that
// CKEditor 5 in modals can work as expected.
_focusTabbable() {
// When the focused element is a CKEditor 5 instance, disable jQuery UI
// focus trap and delegate focus trap to CKEditor 5.
const hasFocus = this._focusedElement
? this._focusedElement.get(0)
: null;
// In case the element is a CKEditor 5 instance, do not change focus
// management.
if (!(hasFocus && hasFocus.ckeditorInstance)) {
oldFocusTabbable.call(this);
}
},
});
})(jQuery);

View File

@ -0,0 +1,20 @@
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
($ => {
const oldFocusTabbable = $.ui.dialog._proto._focusTabbable;
$.widget('ui.dialog', $.ui.dialog, {
_focusTabbable() {
const hasFocus = this._focusedElement ? this._focusedElement.get(0) : null;
if (!(hasFocus && hasFocus.ckeditorInstance)) {
oldFocusTabbable.call(this);
}
}
});
})(jQuery);

View File

@ -4,3 +4,10 @@ ckeditor5_test.off_canvas:
_controller: '\Drupal\ckeditor5_test\Controller\CKEditor5OffCanvasTestController::testOffCanvas'
requirements:
_access: 'TRUE'
ckeditor5_test.dialog:
path: '/ckeditor5_test/dialog'
defaults:
_controller: '\Drupal\ckeditor5_test\Controller\CKEditor5DialogTestController::testDialog'
requirements:
_access: 'TRUE'

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types = 1);
namespace Drupal\ckeditor5_test\Controller;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Url;
/**
* Provides controller for testing CKEditor in off-canvas dialogs.
*/
class CKEditor5DialogTestController {
/**
* Returns a link that can open a node add form in an modal dialog.
*
* @return array
* A render array.
*/
public function testDialog() {
$build['link'] = [
'#type' => 'link',
'#title' => 'Add Node',
'#url' => Url::fromRoute('node.add', ['node_type' => 'page']),
'#attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'dialog',
'data-dialog-options' => Json::encode([
'width' => 700,
'modal' => TRUE,
'autoResize' => TRUE,
]),
],
];
$build['#attached']['library'][] = 'core/drupal.dialog.ajax';
return $build;
}
}

View File

@ -0,0 +1,84 @@
<?php
namespace Drupal\Tests\ckeditor5\FunctionalJavascript;
use Drupal\ckeditor5\Plugin\Editor\CKEditor5;
use Drupal\editor\Entity\Editor;
use Drupal\filter\Entity\FilterFormat;
use Drupal\Tests\ckeditor5\Traits\CKEditor5TestTrait;
use Drupal\user\RoleInterface;
use Symfony\Component\Validator\ConstraintViolation;
/**
* Tests for CKEditor 5 to ensure correct focus management in dialogs.
*
* @group ckeditor5
* @internal
*/
class CKEditor5DialogTest extends CKEditor5TestBase {
use CKEditor5TestTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'ckeditor5',
'ckeditor5_test',
];
/**
* Tests if CKEditor 5 tooltips can be interacted with in dialogs.
*/
public function testCKEditor5FocusInTooltipsInDialog() {
FilterFormat::create([
'format' => 'test_format',
'name' => 'CKEditor 5 with link',
'roles' => [RoleInterface::AUTHENTICATED_ID],
])->save();
Editor::create([
'format' => 'test_format',
'editor' => 'ckeditor5',
'settings' => [
'toolbar' => [
'items' => ['link'],
],
],
])->save();
$this->assertSame([], array_map(
function (ConstraintViolation $v) {
return (string) $v->getMessage();
},
iterator_to_array(CKEditor5::validatePair(
Editor::load('test_format'),
FilterFormat::load('test_format')
))
));
$page = $this->getSession()->getPage();
$assert_session = $this->assertSession();
$this->drupalGet('/ckeditor5_test/dialog');
$page->clickLink('Add Node');
$assert_session->waitForElementVisible('css', '[role="dialog"]');
$assert_session->assertWaitOnAjaxRequest();
$content_area = $assert_session->waitForElementVisible('css', '.ck-editor__editable');
// Focus the editable area first.
$content_area->click();
// Then press the button to add a link.
$this->pressEditorButton('Link');
$link_url = '/ckeditor5_test/dialog';
$input = $assert_session->waitForElementVisible('css', '.ck-balloon-panel input.ck-input-text');
// Make sure the input field can have focus and we can type into it.
$input->setValue($link_url);
// Save the new link.
$page->find('css', '.ck-balloon-panel .ck-button-save')->click();
// Make sure something was added to the text.
$this->assertNotEmpty($content_area->getText());
}
}