From 66c3914d39bf24d828ec8516389d7cadad52294e Mon Sep 17 00:00:00 2001 From: nod_ <nod_@598310.no-reply.drupal.org> Date: Wed, 17 Apr 2024 17:12:06 +0200 Subject: [PATCH] Issue #3296098 by catch, finnsky, smustgrave: Removal :tabbable usage in dialog.js --- .../Drupal/Core/Ajax/OpenDialogCommand.php | 6 ++ .../Core/Ajax/OpenOffCanvasDialogCommand.php | 19 ++++- core/misc/dialog/dialog.jquery-ui.js | 76 +++++++++++++++++++ core/misc/dialog/dialog.js | 2 - .../modules/ckeditor5/ckeditor5.ckeditor5.yml | 3 +- core/modules/editor/js/editor.js | 4 +- .../src/MediaLibraryUiBuilder.php | 4 +- .../src/Controller/TestController.php | 4 +- .../Functional/Ajax/OffCanvasDialogTest.php | 6 +- .../src/Controller/TestController.php | 4 +- .../views_ui/src/Form/Ajax/ViewsFormBase.php | 4 +- .../Ajax/OpenOffCanvasDialogCommandTest.php | 6 +- 12 files changed, 123 insertions(+), 15 deletions(-) diff --git a/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php index a15eaa3211f8..c9ba3e7bb1e4 100644 --- a/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php +++ b/core/lib/Drupal/Core/Ajax/OpenDialogCommand.php @@ -76,6 +76,12 @@ public function __construct($selector, string|\Stringable|null $title, $content, $title = PlainTextOutput::renderFromHtml($title); $dialog_options += ['title' => $title]; + if (isset($dialog_options['dialogClass'])) { + @trigger_error('Passing $dialog_options[\'dialogClass\'] to OpenDialogCommand::__construct() is deprecated in drupal:10.3.0 and will be removed in drupal:12.0.0. Use $dialog_options[\'classes\'] instead. See https://www.drupal.org/node/3440844', E_USER_DEPRECATED); + $dialog_options['classes']['ui-dialog'] = $dialog_options['dialogClass']; + unset($dialog_options['dialogClass']); + } + $this->selector = $selector; $this->content = $content; $this->dialogOptions = $dialog_options; diff --git a/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php b/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php index 1144a189d774..a7aefb00664b 100644 --- a/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php +++ b/core/lib/Drupal/Core/Ajax/OpenOffCanvasDialogCommand.php @@ -38,6 +38,21 @@ class OpenOffCanvasDialogCommand extends OpenDialogCommand { * (optional) The position to render the off-canvas dialog. */ public function __construct(string|\Stringable|null $title, $content, array $dialog_options = [], $settings = NULL, $position = 'side') { + $dialog_class = FALSE; + if (isset($dialog_options['classes']['ui-dialog'])) { + $dialog_class = $dialog_options['classes']['ui-dialog']; + } + elseif (isset($dialog_options['dialogClass'])) { + @trigger_error('Passing $dialog_options[\'dialogClass\'] to OpenOffCanvasDialogCommand::__construct() is deprecated in drupal:10.3.0 and will be removed in drupal:12.0.0. Use $dialog_options[\'classes\'] instead. See https://www.drupal.org/node/3440844', E_USER_DEPRECATED); + $dialog_class = $dialog_options['dialogClass']; + unset($dialog_options['dialogClass']); + } + if ($dialog_class) { + $dialog_options['classes']['ui-dialog'] = $dialog_class . ' ' . "ui-dialog-off-canvas ui-dialog-position-$position"; + } + else { + $dialog_options['classes']['ui-dialog'] = "ui-dialog-off-canvas ui-dialog-position-$position"; + } parent::__construct('#drupal-off-canvas', $title, $content, $dialog_options, $settings); $this->dialogOptions['modal'] = FALSE; $this->dialogOptions['autoResize'] = FALSE; @@ -45,9 +60,7 @@ public function __construct(string|\Stringable|null $title, $content, array $dia $this->dialogOptions['draggable'] = FALSE; $this->dialogOptions['drupalAutoButtons'] = FALSE; $this->dialogOptions['drupalOffCanvasPosition'] = $position; - if (empty($dialog_options['dialogClass'])) { - $this->dialogOptions['dialogClass'] = "ui-dialog-off-canvas ui-dialog-position-$position"; - } + // Add CSS class to #drupal-off-canvas element. This enables developers to // select previous versions of off-canvas styles by using custom selector: // #drupal-off-canvas:not(.drupal-off-canvas-reset). diff --git a/core/misc/dialog/dialog.jquery-ui.js b/core/misc/dialog/dialog.jquery-ui.js index 783797d1a148..32367977d8c8 100644 --- a/core/misc/dialog/dialog.jquery-ui.js +++ b/core/misc/dialog/dialog.jquery-ui.js @@ -30,6 +30,82 @@ $buttons.eq(index).addClass(opts.buttonPrimaryClass); } }, + _createWrapper() { + this.uiDialog = $('<div>') + .hide() + .attr({ + // Setting tabIndex makes the div focusable + tabIndex: -1, + role: 'dialog', + }) + .appendTo(this._appendTo()); + + this._addClass( + this.uiDialog, + 'ui-dialog', + 'ui-widget ui-widget-content ui-front', + ); + this._on(this.uiDialog, { + keydown(event) { + if ( + this.options.closeOnEscape && + !event.isDefaultPrevented() && + event.keyCode && + event.keyCode === $.ui.keyCode.ESCAPE + ) { + event.preventDefault(); + this.close(event); + return; + } + + // Prevent tabbing out of dialogs + if ( + event.keyCode !== $.ui.keyCode.TAB || + event.isDefaultPrevented() + ) { + return; + } + + const tabbableElements = tabbable(this.uiDialog[0]); + if (tabbableElements.length) { + const first = tabbableElements[0]; + const last = tabbableElements[tabbableElements.length - 1]; + + if ( + (event.target === last || event.target === this.uiDialog[0]) && + !event.shiftKey + ) { + this._delay(function () { + $(first).trigger('focus'); + }); + event.preventDefault(); + } else if ( + (event.target === first || event.target === this.uiDialog[0]) && + event.shiftKey + ) { + this._delay(function () { + $(last).trigger('focus'); + }); + event.preventDefault(); + } + } + }, + mousedown(event) { + if (this._moveToTop(event)) { + this._focusTabbable(); + } + }, + }); + + // We assume that any existing aria-describedby attribute means + // that the dialog content is marked up properly + // otherwise we brute force the content as the description + if (!this.element.find('[aria-describedby]').length) { + this.uiDialog.attr({ + 'aria-describedby': this.element.uniqueId().attr('id'), + }); + } + }, // Override jQuery UI's `_focusTabbable()` so finding tabbable elements uses // the core/tabbable library instead of jQuery UI's `:tabbable` selector. _focusTabbable() { diff --git a/core/misc/dialog/dialog.js b/core/misc/dialog/dialog.js index 3ec4218282b2..597ffde1ddf8 100644 --- a/core/misc/dialog/dialog.js +++ b/core/misc/dialog/dialog.js @@ -12,14 +12,12 @@ * @type {object} * * @prop {boolean} [autoOpen=true] - * @prop {string} [dialogClass=''] * @prop {string} [buttonClass='button'] * @prop {string} [buttonPrimaryClass='button--primary'] * @prop {function} close */ drupalSettings.dialog = { autoOpen: true, - dialogClass: '', // Drupal-specific extensions: see dialog.jquery-ui.js. buttonClass: 'button', buttonPrimaryClass: 'button--primary', diff --git a/core/modules/ckeditor5/ckeditor5.ckeditor5.yml b/core/modules/ckeditor5/ckeditor5.ckeditor5.yml index 2163e5e5915a..2a2c3fd16587 100644 --- a/core/modules/ckeditor5/ckeditor5.ckeditor5.yml +++ b/core/modules/ckeditor5/ckeditor5.ckeditor5.yml @@ -820,8 +820,9 @@ media_library_mediaLibrary: name: Drupal.ckeditor5.openDialog invoke: false dialogSettings: + classes: + ui-dialog: media-library-widget-modal height: 75% - dialogClass: media-library-widget-modal drupal: label: Media Library elements: false diff --git a/core/modules/editor/js/editor.js b/core/modules/editor/js/editor.js index cc2d1b9fff6a..b0900e99b614 100644 --- a/core/modules/editor/js/editor.js +++ b/core/modules/editor/js/editor.js @@ -137,7 +137,9 @@ ); const confirmationDialog = Drupal.dialog(`<div>${message}</div>`, { title: Drupal.t('Change text format?'), - dialogClass: 'editor-change-text-format-modal', + classes: { + 'ui-dialog': 'editor-change-text-format-modal', + }, resizable: false, buttons: [ { diff --git a/core/modules/media_library/src/MediaLibraryUiBuilder.php b/core/modules/media_library/src/MediaLibraryUiBuilder.php index 47143fcc251a..f7319244eda5 100644 --- a/core/modules/media_library/src/MediaLibraryUiBuilder.php +++ b/core/modules/media_library/src/MediaLibraryUiBuilder.php @@ -89,7 +89,9 @@ public function __construct(EntityTypeManagerInterface $entity_type_manager, Req */ public static function dialogOptions() { return [ - 'dialogClass' => 'media-library-widget-modal', + 'classes' => [ + 'ui-dialog' => 'media-library-widget-modal', + ], 'title' => t('Add or select media'), 'height' => '75%', 'width' => '75%', diff --git a/core/modules/system/tests/modules/dialog_renderer_test/src/Controller/TestController.php b/core/modules/system/tests/modules/dialog_renderer_test/src/Controller/TestController.php index 00ff48ce4ec4..ac2729c5e915 100644 --- a/core/modules/system/tests/modules/dialog_renderer_test/src/Controller/TestController.php +++ b/core/modules/system/tests/modules/dialog_renderer_test/src/Controller/TestController.php @@ -126,7 +126,9 @@ public function linksDisplay() { 'class' => ['use-ajax'], 'data-dialog-type' => 'modal', 'data-dialog-options' => Json::encode([ - 'dialogClass' => 'no-close', + 'classes' => [ + 'ui-dialog' => 'no-close', + ], ]), ], '#attached' => [ diff --git a/core/modules/system/tests/src/Functional/Ajax/OffCanvasDialogTest.php b/core/modules/system/tests/src/Functional/Ajax/OffCanvasDialogTest.php index 7002e8066371..5b20009d2c42 100644 --- a/core/modules/system/tests/src/Functional/Ajax/OffCanvasDialogTest.php +++ b/core/modules/system/tests/src/Functional/Ajax/OffCanvasDialogTest.php @@ -47,6 +47,10 @@ public function testDialog($position) { 'data' => (string) $dialog_contents, 'dialogOptions' => [ + 'classes' => [ + 'ui-dialog' => 'ui-dialog-off-canvas ui-dialog-position-' . ($position ?: 'side'), + 'ui-dialog-content' => 'drupal-off-canvas-reset', + ], 'title' => 'AJAX Dialog & contents', 'modal' => FALSE, 'autoResize' => FALSE, @@ -54,8 +58,6 @@ public function testDialog($position) { 'draggable' => FALSE, 'drupalAutoButtons' => FALSE, 'drupalOffCanvasPosition' => $position ?: 'side', - 'dialogClass' => 'ui-dialog-off-canvas ui-dialog-position-' . ($position ?: 'side'), - 'classes' => ['ui-dialog-content' => 'drupal-off-canvas-reset'], 'width' => 300, ], 'effect' => 'fade', diff --git a/core/modules/views/tests/modules/views_test_modal/src/Controller/TestController.php b/core/modules/views/tests/modules/views_test_modal/src/Controller/TestController.php index b89cf4081d73..84c0360b746d 100644 --- a/core/modules/views/tests/modules/views_test_modal/src/Controller/TestController.php +++ b/core/modules/views/tests/modules/views_test_modal/src/Controller/TestController.php @@ -22,7 +22,9 @@ public function modal() { 'class' => ['use-ajax'], 'data-dialog-type' => 'modal', 'data-dialog-options' => Json::encode([ - 'dialogClass' => 'views-test-modal', + 'classes' => [ + 'ui-dialog' => 'views-test-modal', + ], 'height' => '50%', 'width' => '50%', 'title' => $this->t('Administer content'), diff --git a/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php b/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php index 78efad376097..806a96d236dd 100644 --- a/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php +++ b/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php @@ -244,7 +244,9 @@ protected function ajaxFormWrapper($form_class, FormStateInterface &$form_state) $display .= $output; $options = [ - 'dialogClass' => 'views-ui-dialog js-views-ui-dialog', + 'classes' => [ + 'ui-dialog' => 'views-ui-dialog js-views-ui-dialog', + ], 'width' => '75%', ]; diff --git a/core/tests/Drupal/Tests/Core/Ajax/OpenOffCanvasDialogCommandTest.php b/core/tests/Drupal/Tests/Core/Ajax/OpenOffCanvasDialogCommandTest.php index 0da679579bbc..1ad22f8905f4 100644 --- a/core/tests/Drupal/Tests/Core/Ajax/OpenOffCanvasDialogCommandTest.php +++ b/core/tests/Drupal/Tests/Core/Ajax/OpenOffCanvasDialogCommandTest.php @@ -34,8 +34,10 @@ public function testRender($position) { 'resizable' => 'w', 'draggable' => FALSE, 'drupalAutoButtons' => FALSE, - 'dialogClass' => 'ui-dialog-off-canvas ui-dialog-position-' . $position, - 'classes' => ['ui-dialog-content' => 'drupal-off-canvas-reset'], + 'classes' => [ + 'ui-dialog' => 'ui-dialog-off-canvas ui-dialog-position-' . $position, + 'ui-dialog-content' => 'drupal-off-canvas-reset', + ], 'width' => 300, 'drupalOffCanvasPosition' => $position, ], -- GitLab