Problem/Motivation
The on-demand source selector from #3528205 opens a prop's source form in a Drupal.dialog (js/source-selector.js). It works with core's jQuery UI dialog. With a theme that replaces Drupal.dialog, like UI Suite Bootstrap, Save and Cancel open a second dialog and nothing is saved.
Four problems in openDialog(). The first one causes the visible bug; the others surfaced while fixing it.
- The dialog leaves the form. The fields stay in the form only because of
appendTo: row. That is a jQuery UI option, and a theme replacingDrupal.dialogmay ignore it: UI Suite Bootstrap builds a Bootstrap modal and appends it to<body>. Save then posts only_triggering_element_name, without the row values. The server rebuilds the row still open, the marker opens a second dialog, and the first one stays in<body>. - The Save/Cancel
mousedownlistener never runs.Drupal.Ajax.prototype.eventResponse()callsevent.stopPropagation(), so a bubbling listener on the dialog content never sees the event. As a resultrefocusRowIdis never set on Save or Cancel, and focus lands on<body>. The check also usesevent.target.matches(), which misses when a theme puts an icon inside the button. And from the keyboard,keypressResponse()re-triggersmousedownthrough jQuery, which no native listener receives. - Escape depends on the
closeoption. Core's dialog calls it. A theme replacingDrupal.dialogmay only dispatchdialog:afterclose, and UI Suite Bootstrap never callsclose, going by itsdialog.js. Dismissing the dialog there would skip Cancel. - The dialog is removed, never closed. Save and Cancel replace the row while the dialog is still open. With a Bootstrap modal,
body.modal-openandoverflow: hiddenthen stay on the page, and it can no longer scroll.
Steps to reproduce
- Enable UI Suite Bootstrap as the theme the component form renders in. We hit this in Display Builder, whose builder renders in the front theme.
- Open a component form and click the cog of a prop, such as Variant.
- Change the value in the dialog and click Save.
- The request carries no
component[variant_id]values, a second dialog opens, and the value is not kept. - Close that dialog:
<body>keepsmodal-openandoverflow: hidden.
Proposed resolution
Keep source-selector.js independent of how the theme builds its dialog:
- After
show(), if the dialog root is not inside the row, move it back in. The root is found by walking up from the content, not by a theme class. - Listen for
dialog:aftercloseon the dialog root, as well as thecloseoption. Afinishedflag makes Cancel run once, whichever fires, or if both do. - Handle Save and Cancel in the capture phase, on
mousedownand on Enter/Spacekeydown, withclosest(). Then close the dialog throughdialog.close(), deferred withsetTimeout(). By then the#ajaxhas sent the fields, closing only hides them, and a keyboardkeypressstill reaches its button.
Tested:
| Theme | Save (mouse, Enter) | Cancel (mouse, Space) | X | Escape |
|---|---|---|---|---|
| UI Suite Bootstrap (Display Builder) | OK | OK | OK | OK |
| Claro (Manage display, component per item formatter) | OK | OK (mouse only tested) | hidden by design | OK |
"OK" means: one dialog, the row values are posted, <body> is left clean, and focus goes back to the cog. Escape sends a single Cancel request.
Remaining tasks
- Review the patch.
- Add a Playwright test with a small test module that replaces
Drupal.dialogand appends to<body>. That covers any such theme without depending on UI Suite Bootstrap. It should assert the values are posted on Save, and that there is one dialog after it. - Add a keyboard case to
source_panel.spec.ts: Save with Enter, then check focus is back on the cog.
User interface changes
With a theme replacing Drupal.dialog, the dialog works as it does with core. Everywhere, focus now goes back to the cog after Save and Cancel.
API changes
None.
Data model changes
None.
Complementary theme issue: UI Suite Bootstrap's Drupal.dialog should honor appendTo and the close option #3622506: Drupal.dialog override ignores the appendTo and close options
Issue fork ui_patterns-3622504
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
mogtofu33 commentedComment #4
mogtofu33 commentedComment #5
mogtofu33 commentedComment #6
pdureau commentedWould it still be relevant if we remove the modal/dialog in #3622928: UX of the asynchronous component form and/or #3622928: UX of the asynchronous component form ?
Comment #7
mogtofu33 commentedSure it will not be relevant anymore, but could be a quick fix on a broken 2.0.x-dev branch.
Comment #8
mogtofu33 commented