diff --git a/packages/ckeditor5-emoji/src/emojipicker.ts b/packages/ckeditor5-emoji/src/emojipicker.ts
index a77945c448f..9007a77dd69 100644
--- a/packages/ckeditor5-emoji/src/emojipicker.ts
+++ b/packages/ckeditor5-emoji/src/emojipicker.ts
@@ -8,7 +8,7 @@
*/
import '../theme/emojipicker.css';
-import { type Locale } from 'ckeditor5/src/utils.js';
+import type { Locale } from 'ckeditor5/src/utils.js';
import { Database } from 'emoji-picker-element';
import { icons, Plugin, type Editor } from 'ckeditor5/src/core.js';
import { Typing } from 'ckeditor5/src/typing.js';
@@ -31,6 +31,8 @@ import {
} from 'ckeditor5/src/ui.js';
import EmojiToneView, { type SkinToneId } from './ui/emojitoneview.js';
+const VISUAL_SELECTION_MARKER_NAME = 'emoji-picker';
+
/**
* The emoji picker plugin.
*
@@ -122,6 +124,33 @@ export default class EmojiPicker extends Plugin {
this._getEmojiGroup( { databaseId: 8, title: 'Symbols', exampleEmoji: '🟢' } ),
this._getEmojiGroup( { databaseId: 9, title: 'Flags', exampleEmoji: '🏁' } )
] );
+
+ // Renders a fake visual selection marker on an expanded selection.
+ editor.conversion.for( 'editingDowncast' ).markerToHighlight( {
+ model: VISUAL_SELECTION_MARKER_NAME,
+ view: {
+ classes: [ 'ck-fake-emoji-selection' ]
+ }
+ } );
+
+ // Renders a fake visual selection marker on a collapsed selection.
+ editor.conversion.for( 'editingDowncast' ).markerToElement( {
+ model: VISUAL_SELECTION_MARKER_NAME,
+ view: ( data, { writer } ) => {
+ if ( !data.markerRange.isCollapsed ) {
+ return null;
+ }
+
+ const markerElement = writer.createUIElement( 'span' );
+
+ writer.addClass(
+ [ 'ck-fake-emoji-selection', 'ck-fake-emoji-selection_collapsed' ],
+ markerElement
+ );
+
+ return markerElement;
+ }
+ } );
}
private async _getEmojiGroup( {
@@ -321,6 +350,8 @@ export default class EmojiPicker extends Plugin {
if ( this._searchQuery ) {
dropdownPanelContent.searchView.setSearchQuery( this._searchQuery );
}
+
+ this._showFakeVisualSelection();
}
/**
@@ -333,6 +364,8 @@ export default class EmojiPicker extends Plugin {
this.editor.editing.view.focus();
this._searchQuery = '';
+
+ this._hideFakeVisualSelection();
}
private _getBalloonPositionData() {
@@ -346,6 +379,51 @@ export default class EmojiPicker extends Plugin {
target
};
}
+
+ /**
+ * Displays a fake visual selection when the contextual balloon is displayed.
+ *
+ * This adds an 'emoji-picker' marker into the document that is rendered as a highlight on selected text fragment.
+ */
+ private _showFakeVisualSelection(): void {
+ const model = this.editor.model;
+
+ model.change( writer => {
+ const range = model.document.selection.getFirstRange()!;
+
+ if ( range.start.isAtEnd ) {
+ const startPosition = range.start.getLastMatchingPosition(
+ ( { item } ) => !model.schema.isContent( item ),
+ { boundaries: range }
+ );
+
+ writer.addMarker( VISUAL_SELECTION_MARKER_NAME, {
+ usingOperation: false,
+ affectsData: false,
+ range: writer.createRange( startPosition, range.end )
+ } );
+ } else {
+ writer.addMarker( VISUAL_SELECTION_MARKER_NAME, {
+ usingOperation: false,
+ affectsData: false,
+ range
+ } );
+ }
+ } );
+ }
+
+ /**
+ * Hides the fake visual selection created in {@link #_showFakeVisualSelection}.
+ */
+ private _hideFakeVisualSelection(): void {
+ const model = this.editor.model;
+
+ if ( model.markers.has( VISUAL_SELECTION_MARKER_NAME ) ) {
+ model.change( writer => {
+ writer.removeMarker( VISUAL_SELECTION_MARKER_NAME );
+ } );
+ }
+ }
}
export interface DropdownPanelContent {
diff --git a/packages/ckeditor5-emoji/src/ui/emojicategoriesview.ts b/packages/ckeditor5-emoji/src/ui/emojicategoriesview.ts
index 8475a61f446..19190ec1abd 100644
--- a/packages/ckeditor5-emoji/src/ui/emojicategoriesview.ts
+++ b/packages/ckeditor5-emoji/src/ui/emojicategoriesview.ts
@@ -1,6 +1,6 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
diff --git a/packages/ckeditor5-emoji/src/ui/emojigridview.ts b/packages/ckeditor5-emoji/src/ui/emojigridview.ts
index 673d079ba0b..7368ffa55ea 100644
--- a/packages/ckeditor5-emoji/src/ui/emojigridview.ts
+++ b/packages/ckeditor5-emoji/src/ui/emojigridview.ts
@@ -1,6 +1,6 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
diff --git a/packages/ckeditor5-emoji/src/ui/emojiinfoview.ts b/packages/ckeditor5-emoji/src/ui/emojiinfoview.ts
index 77df486ee97..12de7f779d0 100644
--- a/packages/ckeditor5-emoji/src/ui/emojiinfoview.ts
+++ b/packages/ckeditor5-emoji/src/ui/emojiinfoview.ts
@@ -1,6 +1,6 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
diff --git a/packages/ckeditor5-emoji/src/ui/emojipickerview.ts b/packages/ckeditor5-emoji/src/ui/emojipickerview.ts
index 032958e8a08..99189c25ec6 100644
--- a/packages/ckeditor5-emoji/src/ui/emojipickerview.ts
+++ b/packages/ckeditor5-emoji/src/ui/emojipickerview.ts
@@ -1,6 +1,6 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
diff --git a/packages/ckeditor5-emoji/src/ui/emojisearchview.ts b/packages/ckeditor5-emoji/src/ui/emojisearchview.ts
index 8ae250fe3a0..451565f2e17 100644
--- a/packages/ckeditor5-emoji/src/ui/emojisearchview.ts
+++ b/packages/ckeditor5-emoji/src/ui/emojisearchview.ts
@@ -1,6 +1,6 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
diff --git a/packages/ckeditor5-emoji/src/ui/emojitoneview.ts b/packages/ckeditor5-emoji/src/ui/emojitoneview.ts
index d6e1d28aeda..ce18e12334a 100644
--- a/packages/ckeditor5-emoji/src/ui/emojitoneview.ts
+++ b/packages/ckeditor5-emoji/src/ui/emojitoneview.ts
@@ -1,6 +1,6 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options
*/
/**
diff --git a/packages/ckeditor5-emoji/tests/emojipicker.js b/packages/ckeditor5-emoji/tests/emojipicker.js
index 2449ba7bfb6..8bc8d96e87b 100644
--- a/packages/ckeditor5-emoji/tests/emojipicker.js
+++ b/packages/ckeditor5-emoji/tests/emojipicker.js
@@ -8,10 +8,11 @@
import { ContextualBalloon, Dialog } from 'ckeditor5/src/ui.js';
import { EmojiPicker } from '../src/index.js';
import { Essentials } from '@ckeditor/ckeditor5-essentials';
-import { getData as getModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model.js';
import { Paragraph } from '@ckeditor/ckeditor5-paragraph';
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js';
import { Typing } from '@ckeditor/ckeditor5-typing';
+import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view.js';
+import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model.js';
describe( 'EmojiPicker', () => {
let editor, editorElement, emojiPicker;
@@ -204,6 +205,245 @@ describe( 'EmojiPicker', () => {
sinon.assert.calledOnce( spy );
} );
+
+ describe( 'fake visual selection', () => {
+ describe( 'non-collapsed', () => {
+ it( 'should be displayed when a text fragment is selected', () => {
+ setModelData( editor.model, '
f{o}o
' ); + expect( editor.getData() ).to.equal( 'foo
' ); + } ); + + it( 'should display a fake visual selection on the next non-empty text node when selection starts at the end ' + + 'of the empty block in the multiline selection', () => { + setModelData( editor.model, '[
foo]
' + ); + expect( editor.getData() ).to.equal( '
foo
' ); + } ); + + it( 'should display a fake visual selection on the next non-empty text node when selection starts at the end ' + + 'of the first block in the multiline selection', () => { + setModelData( editor.model, 'foo{
bar]
' + ); + expect( editor.getData() ).to.equal( 'foo
bar
' ); + } ); + + it( 'should be displayed on first text node in non-empty element when selection contains few empty elements', () => { + setModelData( editor.model, [ + 'foo{
', + '', + '', + 'bar
', + '', + '', + '}baz
' + ].join( '' ); + + expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData ); + expect( editor.getData() ).to.equal( [ + 'foo
', + '
', + '
bar
', + '
', + '
baz
' + ].join( '' ) ); + } ); + } ); + + describe( 'collapsed', () => { + it( 'should be displayed on a collapsed selection', () => { + setModelData( editor.model, 'f{}o
' + ); + expect( editor.getData() ).to.equal( 'fo
' ); + } ); + + it( 'should be displayed on selection focus when selection contains only one empty element ' + + '(selection focus is at the beginning of the first non-empty element)', () => { + setModelData( editor.model, [ + 'foo{
', + '', + ']bar
' + ].join( '' ); + + expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData ); + expect( editor.getData() ).to.equal( 'foo
bar
' ); + } ); + + it( 'should be displayed on selection focus when selection contains few empty elements ' + + '(selection focus is at the beginning of the first non-empty element)', () => { + setModelData( editor.model, [ + 'foo{
', + '', + '', + ']bar
' + ].join( '' ); + + expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData ); + expect( editor.getData() ).to.equal( 'foo
bar
' ); + } ); + + it( 'should be displayed on selection focus when selection contains few empty elements ' + + '(selection focus is inside an empty element)', () => { + setModelData( editor.model, [ + 'foo{
', + '', + ']
', + 'bar
' + ].join( '' ); + + expect( getViewData( editor.editing.view ) ).to.equal( expectedViewData ); + expect( editor.getData() ).to.equal( 'foo
bar
' ); + } ); + } ); + } ); } ); function clickEmojiToolbarButton() { diff --git a/packages/ckeditor5-emoji/tests/manual/emoji.js b/packages/ckeditor5-emoji/tests/manual/emoji.js index e02832d4dab..efb4891850c 100644 --- a/packages/ckeditor5-emoji/tests/manual/emoji.js +++ b/packages/ckeditor5-emoji/tests/manual/emoji.js @@ -3,7 +3,7 @@ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ -/* globals console:false, window, document */ +/* globals console, window, document */ import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor.js'; import { Emoji } from '../../src/index.js'; diff --git a/packages/ckeditor5-emoji/tests/ui/emojicategoriesview.js b/packages/ckeditor5-emoji/tests/ui/emojicategoriesview.js index 2c4ba67dd7b..fcb3b31ef1f 100644 --- a/packages/ckeditor5-emoji/tests/ui/emojicategoriesview.js +++ b/packages/ckeditor5-emoji/tests/ui/emojicategoriesview.js @@ -1,6 +1,6 @@ /** * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ import { keyCodes } from '@ckeditor/ckeditor5-utils'; diff --git a/packages/ckeditor5-emoji/tests/ui/emojigridview.js b/packages/ckeditor5-emoji/tests/ui/emojigridview.js index 704997e5e52..67646b3e265 100644 --- a/packages/ckeditor5-emoji/tests/ui/emojigridview.js +++ b/packages/ckeditor5-emoji/tests/ui/emojigridview.js @@ -1,6 +1,6 @@ /** * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /* global document, window */ diff --git a/packages/ckeditor5-emoji/tests/ui/emojiinfoview.js b/packages/ckeditor5-emoji/tests/ui/emojiinfoview.js index d0d5f09c311..64b8bb7600c 100644 --- a/packages/ckeditor5-emoji/tests/ui/emojiinfoview.js +++ b/packages/ckeditor5-emoji/tests/ui/emojiinfoview.js @@ -1,6 +1,6 @@ /** * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ import EmojiInfoView from '../../src/ui/emojiinfoview.js'; diff --git a/packages/ckeditor5-emoji/tests/ui/emojipickerview.js b/packages/ckeditor5-emoji/tests/ui/emojipickerview.js index e5c9f6da30b..3c8d918099b 100644 --- a/packages/ckeditor5-emoji/tests/ui/emojipickerview.js +++ b/packages/ckeditor5-emoji/tests/ui/emojipickerview.js @@ -1,6 +1,6 @@ /** * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ /* globals document */ diff --git a/packages/ckeditor5-emoji/tests/ui/emojisearchview.js b/packages/ckeditor5-emoji/tests/ui/emojisearchview.js index ddb534f92c8..2f8d3a76d4c 100644 --- a/packages/ckeditor5-emoji/tests/ui/emojisearchview.js +++ b/packages/ckeditor5-emoji/tests/ui/emojisearchview.js @@ -1,6 +1,6 @@ /** * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ import EmojiSearchView from '../../src/ui/emojisearchview.js'; diff --git a/packages/ckeditor5-emoji/tests/ui/emojitoneview.js b/packages/ckeditor5-emoji/tests/ui/emojitoneview.js index e5a24d630cc..a0506469a20 100644 --- a/packages/ckeditor5-emoji/tests/ui/emojitoneview.js +++ b/packages/ckeditor5-emoji/tests/ui/emojitoneview.js @@ -1,6 +1,6 @@ /** * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ import EmojiToneView from '../../src/ui/emojitoneview.js'; diff --git a/packages/ckeditor5-emoji/theme/emojicategories.css b/packages/ckeditor5-emoji/theme/emojicategories.css index fb6599be95e..16109ae237e 100644 --- a/packages/ckeditor5-emoji/theme/emojicategories.css +++ b/packages/ckeditor5-emoji/theme/emojicategories.css @@ -1,6 +1,6 @@ /* * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ .ck.ck-emoji-categories .ck.ck-button.ck-button_with-text { diff --git a/packages/ckeditor5-emoji/theme/emojigrid.css b/packages/ckeditor5-emoji/theme/emojigrid.css index ee652c4930f..babf5e92880 100644 --- a/packages/ckeditor5-emoji/theme/emojigrid.css +++ b/packages/ckeditor5-emoji/theme/emojigrid.css @@ -1,6 +1,6 @@ /* * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ .ck.ck-emoji-grid { diff --git a/packages/ckeditor5-emoji/theme/emojiinfo.css b/packages/ckeditor5-emoji/theme/emojiinfo.css index efbff8dd677..6cec320e91f 100644 --- a/packages/ckeditor5-emoji/theme/emojiinfo.css +++ b/packages/ckeditor5-emoji/theme/emojiinfo.css @@ -1,6 +1,6 @@ /* * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ .ck.ck-emoji-info { diff --git a/packages/ckeditor5-emoji/theme/emojipicker.css b/packages/ckeditor5-emoji/theme/emojipicker.css index f836b4fff77..750b75f7c7e 100644 --- a/packages/ckeditor5-emoji/theme/emojipicker.css +++ b/packages/ckeditor5-emoji/theme/emojipicker.css @@ -16,3 +16,19 @@ padding: 10px; justify-content: space-between; } + +/* + * Classes used by the "fake visual selection" displayed in the content when an input + * in the emoji picker UI has focus (the browser does not render the native selection in this state). + */ +.ck .ck-fake-emoji-selection { + background: var(--ck-color-link-fake-selection); +} + +/* A collapsed fake visual selection. */ +.ck .ck-fake-emoji-selection_collapsed { + height: 100%; + border-right: 1px solid var(--ck-color-base-text); + margin-right: -1px; + outline: solid 1px hsla(0, 0%, 100%, .5); +} diff --git a/packages/ckeditor5-emoji/theme/emojitone.css b/packages/ckeditor5-emoji/theme/emojitone.css index 527394a3d2e..e30c21f382f 100644 --- a/packages/ckeditor5-emoji/theme/emojitone.css +++ b/packages/ckeditor5-emoji/theme/emojitone.css @@ -1,6 +1,6 @@ /* * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ .ck.ck-emoji-tone { diff --git a/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emoji.css b/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emoji.css index 1479f44a320..470dd362665 100644 --- a/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emoji.css +++ b/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emoji.css @@ -1,6 +1,6 @@ /* * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ @import "@ckeditor/ckeditor5-ui/theme/mixins/_dir.css"; diff --git a/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emojigrid.css b/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emojigrid.css index d9ce7dbf56f..f81dc7be354 100644 --- a/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emojigrid.css +++ b/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emojigrid.css @@ -1,6 +1,6 @@ /* * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ @import "@ckeditor/ckeditor5-ui/theme/mixins/_rwd.css"; diff --git a/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emojiinfo.css b/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emojiinfo.css index 93d9f2ada42..a244fa4c271 100644 --- a/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emojiinfo.css +++ b/packages/ckeditor5-theme-lark/theme/ckeditor5-emoji/emojiinfo.css @@ -1,6 +1,6 @@ /* * Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. - * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license + * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-licensing-options */ @import "@ckeditor/ckeditor5-ui/theme/mixins/_rwd.css";