Problem/Motivation

In Safari and Firefox, when a CKEditor 5 balloon panel (e.g. the link dialog) is open and the user clicks outside the inline editor, the toolbar remains visible as collapsed three-dots in .ck-body-wrapper. Clicking this artifact opens the toolbar in an arbitrary screen position, allowing formatting operations to queue up and apply simultaneously when the editor is refocused.

This is caused by an upstream CKEditor 5 bug (#18978). The link plugin's _removeFormView() unconditionally calls saveButtonView.focus() during balloon cleanup, which re-triggers the FocusTracker's _focus() method and sets isFocused back to true. The inline toolbar panel is bound to isFocused, so it stays visible in the now-hidden container.

In Chrome, document.activeElement moves away from hidden elements synchronously, so the FocusTracker's deferred blur eventually fires. In Safari and Firefox, document.activeElement doesn't move eagerly, so the blur guard in FocusTracker._blur() suppresses the blur entirely and isFocused stays true permanently.

Steps to reproduce

  1. Open a WYSIWYG editor field using the Change tool (Safari or Firefox)
  2. Create or select a link to open the link balloon
  3. Click outside the editor field without closing the link balloon
  4. Observe the three-dots artifact on screen
  5. Click the artifact — the toolbar appears in an arbitrary location
  6. Click formatting buttons, then click back into the editor — all queued changes apply at once

Proposed resolution

Add a defocusCkeditor() method to the InlineEditor field plugin that temporarily disables FocusTracker._focus() before the container is hidden, preventing CKEditor's balloon cleanup from re-asserting focus. The method:

  1. No-ops editor.ui.focusTracker._focus
  2. Sets isFocused = false and focusedElement = null
  3. Restores _focus via setTimeout(0) after the event cycle completes

Called from blurElement() before replaceFormItemWithPageElement() hides the container.

This is a workaround for the upstream CKEditor 5 bug. The upstream fix is a one-line change: making saveButtonView.focus() in _removeFormView() conditional on the updateFocus parameter it already accepts. This method should be removed when the upstream fix lands in Drupal core's vendored CKEditor 5.

Remaining tasks

  • Review and merge the MR
  • Monitor ckeditor/ckeditor5#18978 for upstream fix
  • Remove defocusCkeditor() when upstream fix is included in Drupal core's CKEditor 5 vendor

Comments

tim bozeman created an issue. See original summary.

  • tim bozeman committed eacd4059 on 2.3.x
    feat: #3587702 CKEditor 5 inline toolbar stays visible as orphaned three...
tim bozeman’s picture

Status: Active » Fixed

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.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.