diff --git a/docs/_snippets/examples/multi-root-editor.js b/docs/_snippets/examples/multi-root-editor.js index da09bf79517..b04537ca6b4 100644 --- a/docs/_snippets/examples/multi-root-editor.js +++ b/docs/_snippets/examples/multi-root-editor.js @@ -305,7 +305,8 @@ class MultirootEditorUI extends EditorUI { view: editingView, element: editingRoot, text: placeholderText, - isDirectHost: false + isDirectHost: false, + keepOnFocus: true } ); } } diff --git a/docs/framework/guides/custom-editor-creator.md b/docs/framework/guides/custom-editor-creator.md index f09da06ca12..cce2c998ad2 100644 --- a/docs/framework/guides/custom-editor-creator.md +++ b/docs/framework/guides/custom-editor-creator.md @@ -295,7 +295,8 @@ class MultirootEditorUI extends EditorUI { view: editingView, element: editingRoot, text: placeholderText, - isDirectHost: false + isDirectHost: false, + keepOnFocus: true } ); } } diff --git a/packages/ckeditor5-editor-balloon/src/ballooneditorui.js b/packages/ckeditor5-editor-balloon/src/ballooneditorui.js index 16d68e5fb73..e3ccc93fd64 100644 --- a/packages/ckeditor5-editor-balloon/src/ballooneditorui.js +++ b/packages/ckeditor5-editor-balloon/src/ballooneditorui.js @@ -134,7 +134,8 @@ export default class BalloonEditorUI extends EditorUI { view: editingView, element: editingRoot, text: placeholderText, - isDirectHost: false + isDirectHost: false, + keepOnFocus: true } ); } } diff --git a/packages/ckeditor5-editor-classic/src/classiceditorui.js b/packages/ckeditor5-editor-classic/src/classiceditorui.js index f36f4805a91..6fbb1a15e55 100644 --- a/packages/ckeditor5-editor-classic/src/classiceditorui.js +++ b/packages/ckeditor5-editor-classic/src/classiceditorui.js @@ -176,7 +176,8 @@ export default class ClassicEditorUI extends EditorUI { view: editingView, element: editingRoot, text: placeholderText, - isDirectHost: false + isDirectHost: false, + keepOnFocus: true } ); } } diff --git a/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js b/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js index 5f95f73c72a..f000aa624e8 100644 --- a/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js +++ b/packages/ckeditor5-editor-decoupled/src/decouplededitorui.js @@ -134,7 +134,8 @@ export default class DecoupledEditorUI extends EditorUI { view: editingView, element: editingRoot, text: placeholderText, - isDirectHost: false + isDirectHost: false, + keepOnFocus: true } ); } } diff --git a/packages/ckeditor5-editor-inline/src/inlineeditorui.js b/packages/ckeditor5-editor-inline/src/inlineeditorui.js index 8c1c324a97f..fb24bb678d2 100644 --- a/packages/ckeditor5-editor-inline/src/inlineeditorui.js +++ b/packages/ckeditor5-editor-inline/src/inlineeditorui.js @@ -170,7 +170,8 @@ export default class InlineEditorUI extends EditorUI { view: editingView, element: editingRoot, text: placeholderText, - isDirectHost: false + isDirectHost: false, + keepOnFocus: true } ); } } diff --git a/packages/ckeditor5-engine/src/view/placeholder.js b/packages/ckeditor5-engine/src/view/placeholder.js index 7ed702c8eeb..f5c6e7d4e7e 100644 --- a/packages/ckeditor5-engine/src/view/placeholder.js +++ b/packages/ckeditor5-engine/src/view/placeholder.js @@ -29,9 +29,10 @@ const documentPlaceholders = new WeakMap(); * in the passed `element` but in one of its children (selected automatically, i.e. a first empty child element). * Useful when attaching placeholders to elements that can host other elements (not just text), for instance, * editable root elements. + * @param {Boolean} [options.keepOnFocus=false] If set `true`, the placeholder stay visible when the host element is focused. */ export function enablePlaceholder( options ) { - const { view, element, text, isDirectHost = true } = options; + const { view, element, text, isDirectHost = true, keepOnFocus = false } = options; const doc = view.document; // Use a single a single post fixer per—document to update all placeholders. @@ -46,7 +47,8 @@ export function enablePlaceholder( options ) { // Store information about the element placeholder under its document. documentPlaceholders.get( doc ).set( element, { text, - isDirectHost + isDirectHost, + keepOnFocus } ); // Update the placeholders right away. @@ -140,22 +142,32 @@ export function hidePlaceholder( writer, element ) { * {@link module:engine/view/placeholder~enablePlaceholder `enablePlaceholder()`} in that case or make * sure the correct element is passed to the helper. * - * @param {module:engine/view/element~Element} element + * @param {module:engine/view/element~Element} element Element that holds the placeholder. + * @param {Boolean} keepOnFocus Focusing the element will keep the placeholder visible. * @returns {Boolean} */ -export function needsPlaceholder( element ) { +export function needsPlaceholder( element, keepOnFocus ) { if ( !element.isAttached() ) { return false; } - // The element is empty only as long as it contains nothing but uiElements. - const isEmptyish = !Array.from( element.getChildren() ) + // Anything but uiElement(s) counts as content. + const hasContent = Array.from( element.getChildren() ) .some( element => !element.is( 'uiElement' ) ); + if ( hasContent ) { + return false; + } + + // Skip the focus check and make the placeholder visible already regardless of document focus state. + if ( keepOnFocus ) { + return true; + } + const doc = element.document; - // If the element is empty and the document is blurred. - if ( !doc.isFocused && isEmptyish ) { + // If the document is blurred. + if ( !doc.isFocused ) { return true; } @@ -163,11 +175,7 @@ export function needsPlaceholder( element ) { const selectionAnchor = viewSelection.anchor; // If document is focused and the element is empty but the selection is not anchored inside it. - if ( isEmptyish && selectionAnchor && selectionAnchor.parent !== element ) { - return true; - } - - return false; + return selectionAnchor && selectionAnchor.parent !== element; } // Updates all placeholders associated with a document in a post–fixer callback. @@ -221,7 +229,7 @@ function updatePlaceholder( writer, element, config ) { wasViewModified = true; } - if ( needsPlaceholder( hostElement ) ) { + if ( needsPlaceholder( hostElement, config.keepOnFocus ) ) { if ( showPlaceholder( writer, hostElement ) ) { wasViewModified = true; } diff --git a/packages/ckeditor5-engine/tests/view/placeholder.js b/packages/ckeditor5-engine/tests/view/placeholder.js index dbe345b415c..7efd9eac679 100644 --- a/packages/ckeditor5-engine/tests/view/placeholder.js +++ b/packages/ckeditor5-engine/tests/view/placeholder.js @@ -265,6 +265,58 @@ describe( 'placeholder', () => { expect( viewRoot.getChild( 0 ).hasAttribute( 'data-placeholder' ) ).to.be.false; expect( viewRoot.getChild( 0 ).hasClass( 'ck-placeholder' ) ).to.be.false; } ); + + it( 'should keep the placeholder visible when the host element is focused (keepOnFocus = true)', () => { + setData( view, '
{moo}
{}foo
' + + '' + ); + + const caption = doc.getRoot().getNodeByPath( [ 1, 0 ] ); + + editor.editing.view.document.isFocused = true; + editor.focus(); + + model.change( writer => { + writer.setSelection( writer.createRangeIn( caption ) ); + } ); + + expect( getViewData( view ) ).to.equal( + 'foo
' + + '' + ); + } ); + it( 'should not add additional figcaption if one is already present', () => { setModelData( model, '