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 replacing Drupal.dialog may 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 mousedown listener never runs. Drupal.Ajax.prototype.eventResponse() calls event.stopPropagation(), so a bubbling listener on the dialog content never sees the event. As a result refocusRowId is never set on Save or Cancel, and focus lands on <body>. The check also uses event.target.matches(), which misses when a theme puts an icon inside the button. And from the keyboard, keypressResponse() re-triggers mousedown through jQuery, which no native listener receives.
  • Escape depends on the close option. Core's dialog calls it. A theme replacing Drupal.dialog may only dispatch dialog:afterclose, and UI Suite Bootstrap never calls close, going by its dialog.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-open and overflow: hidden then 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> keeps modal-open and overflow: 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:afterclose on the dialog root, as well as the close option. A finished flag makes Cancel run once, whichever fires, or if both do.
  • Handle Save and Cancel in the capture phase, on mousedown and on Enter/Space keydown, with closest(). Then close the dialog through dialog.close(), deferred with setTimeout(). By then the #ajax has sent the fields, closing only hides them, and a keyboard keypress still 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.dialog and 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

Command icon 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

mogtofu33 created an issue. See original summary.

mogtofu33’s picture

Issue summary: View changes
mogtofu33’s picture

Issue tags: +AI-accelerated
mogtofu33’s picture

Issue summary: View changes
pdureau’s picture

Would 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 ?

mogtofu33’s picture

Sure it will not be relevant anymore, but could be a quick fix on a broken 2.0.x-dev branch.

mogtofu33’s picture

Status: Needs review » Closed (outdated)

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.