Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit a0ad8fe

Browse files
authored
Merge pull request #1330 from ckeditor/t/1329
Feature: Introduced composition observer. Closes #1329.
2 parents 2462d27 + 309f010 commit a0ad8fe

File tree

8 files changed

+249
-1
lines changed

8 files changed

+249
-1
lines changed

src/view/document.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ export default class Document {
6666
*/
6767
this.set( 'isFocused', false );
6868

69+
/**
70+
* True if composition is in progress inside the document.
71+
*
72+
* This property is updated by the {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
73+
* If the {@link module:engine/view/observer/compositionobserver~CompositionObserver} is disabled this property will not change.
74+
*
75+
* @readonly
76+
* @observable
77+
* @member {Boolean} module:engine/view/document~Document#isComposing
78+
*/
79+
this.set( 'isComposing', false );
80+
6981
/**
7082
* Post-fixer callbacks registered to the view document.
7183
*
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md.
4+
*/
5+
6+
/**
7+
* @module engine/view/observer/compositionobserver
8+
*/
9+
10+
import DomEventObserver from './domeventobserver';
11+
12+
/**
13+
* {@link module:engine/view/document~Document#event:compositionstart Compositionstart},
14+
* {@link module:engine/view/document~Document#event:compositionupdate compositionupdate} and
15+
* {@link module:engine/view/document~Document#event:compositionend compositionend} events observer.
16+
*
17+
* Note that this observer is attached by the {@link module:engine/view/view~View} and is available by default.
18+
*
19+
* @extends module:engine/view/observer/domeventobserver~DomEventObserver
20+
*/
21+
export default class CompositionObserver extends DomEventObserver {
22+
constructor( view ) {
23+
super( view );
24+
25+
this.domEventType = [ 'compositionstart', 'compositionupdate', 'compositionend' ];
26+
const document = this.document;
27+
28+
document.on( 'compositionstart', () => {
29+
document.isComposing = true;
30+
} );
31+
32+
document.on( 'compositionend', () => {
33+
document.isComposing = false;
34+
} );
35+
}
36+
37+
onDomEvent( domEvent ) {
38+
this.fire( domEvent.type, domEvent );
39+
}
40+
}
41+
42+
/**
43+
* Fired when composition starts inside one of the editables.
44+
*
45+
* Introduced by {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
46+
*
47+
* Note that because {@link module:engine/view/observer/compositionobserver~CompositionObserver} is attached by the
48+
* {@link module:engine/view/view~View} this event is available by default.
49+
*
50+
* @see module:engine/view/observer/compositionobserver~CompositionObserver
51+
* @event module:engine/view/document~Document#event:compositionstart
52+
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
53+
*/
54+
55+
/**
56+
* Fired when composition is updated inside one of the editables.
57+
*
58+
* Introduced by {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
59+
*
60+
* Note that because {@link module:engine/view/observer/compositionobserver~CompositionObserver} is attached by the
61+
* {@link module:engine/view/view~View} this event is available by default.
62+
*
63+
* @see module:engine/view/observer/compositionobserver~CompositionObserver
64+
* @event module:engine/view/document~Document#event:compositionupdate
65+
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
66+
*/
67+
68+
/**
69+
* Fired when composition ends inside one of the editables.
70+
*
71+
* Introduced by {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
72+
*
73+
* Note that because {@link module:engine/view/observer/compositionobserver~CompositionObserver} is attached by the
74+
* {@link module:engine/view/view~View} this event is available by default.
75+
*
76+
* @see module:engine/view/observer/compositionobserver~CompositionObserver
77+
* @event module:engine/view/document~Document#event:compositionend
78+
* @param {module:engine/view/observer/domeventdata~DomEventData} data Event data.
79+
*/

src/view/view.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import KeyObserver from './observer/keyobserver';
1717
import FakeSelectionObserver from './observer/fakeselectionobserver';
1818
import SelectionObserver from './observer/selectionobserver';
1919
import FocusObserver from './observer/focusobserver';
20+
import CompositionObserver from './observer/compositionobserver';
2021

2122
import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
2223
import log from '@ckeditor/ckeditor5-utils/src/log';
@@ -47,6 +48,7 @@ import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
4748
* * {@link module:engine/view/observer/focusobserver~FocusObserver},
4849
* * {@link module:engine/view/observer/keyobserver~KeyObserver},
4950
* * {@link module:engine/view/observer/fakeselectionobserver~FakeSelectionObserver}.
51+
* * {@link module:engine/view/observer/compositionobserver~CompositionObserver}.
5052
*
5153
* This class also {@link module:engine/view/view~View#attachDomRoot bind DOM and View elements}.
5254
*
@@ -138,6 +140,7 @@ export default class View {
138140
this.addObserver( FocusObserver );
139141
this.addObserver( KeyObserver );
140142
this.addObserver( FakeSelectionObserver );
143+
this.addObserver( CompositionObserver );
141144

142145
// Inject quirks handlers.
143146
injectQuirksHandling( this );
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div id="editor">
2+
<p>foo bar</p>
3+
</div>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md.
4+
*/
5+
6+
/* globals console, window, document */
7+
8+
import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
9+
import Typing from '@ckeditor/ckeditor5-typing/src/typing';
10+
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
11+
12+
ClassicEditor
13+
.create( document.querySelector( '#editor' ), {
14+
plugins: [ Typing, Paragraph ]
15+
} )
16+
.then( editor => {
17+
window.editor = editor;
18+
19+
const view = editor.editing.view;
20+
const viewDocument = view.document;
21+
22+
viewDocument.on( 'compositionstart', ( evt, data ) => console.log( 'compositionstart', data ) );
23+
viewDocument.on( 'compositionupdate', ( evt, data ) => console.log( 'compositionupdate', data ) );
24+
viewDocument.on( 'compositionend', ( evt, data ) => console.log( 'compositionend', data ) );
25+
26+
view.focus();
27+
} )
28+
.catch( err => {
29+
console.error( err.stack );
30+
} );
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
* Expected initialization: `{}foo bar`.
2+
* Check whether composition events are logged to the console with proper data:
3+
* `compositionstart`,
4+
* `compositionupdate`,
5+
* `compositionend`
6+
7+
**Composition events are fired while typing:**
8+
* Hiragana,
9+
* Spanish-ISO: accent `' + a`,
10+
* MacOS: long `a` press (accent balloon)
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
3+
* For licensing, see LICENSE.md.
4+
*/
5+
6+
/* globals document */
7+
import CompositionObserver from '../../../src/view/observer/compositionobserver';
8+
import View from '../../../src/view/view';
9+
10+
describe( 'CompositionObserver', () => {
11+
let view, viewDocument, observer;
12+
13+
beforeEach( () => {
14+
view = new View();
15+
viewDocument = view.document;
16+
observer = view.getObserver( CompositionObserver );
17+
} );
18+
19+
afterEach( () => {
20+
view.destroy();
21+
} );
22+
23+
it( 'should define domEventType', () => {
24+
expect( observer.domEventType ).to.deep.equal( [ 'compositionstart', 'compositionupdate', 'compositionend' ] );
25+
} );
26+
27+
describe( 'onDomEvent', () => {
28+
it( 'should fire compositionstart with the right event data', () => {
29+
const spy = sinon.spy();
30+
31+
viewDocument.on( 'compositionstart', spy );
32+
33+
observer.onDomEvent( { type: 'compositionstart', target: document.body } );
34+
35+
expect( spy.calledOnce ).to.be.true;
36+
37+
const data = spy.args[ 0 ][ 1 ];
38+
expect( data.domTarget ).to.equal( document.body );
39+
} );
40+
41+
it( 'should fire compositionupdate with the right event data', () => {
42+
const spy = sinon.spy();
43+
44+
viewDocument.on( 'compositionupdate', spy );
45+
46+
observer.onDomEvent( { type: 'compositionupdate', target: document.body } );
47+
48+
expect( spy.calledOnce ).to.be.true;
49+
50+
const data = spy.args[ 0 ][ 1 ];
51+
expect( data.domTarget ).to.equal( document.body );
52+
} );
53+
54+
it( 'should fire compositionend with the right event data', () => {
55+
const spy = sinon.spy();
56+
57+
viewDocument.on( 'compositionend', spy );
58+
59+
observer.onDomEvent( { type: 'compositionend', target: document.body } );
60+
61+
expect( spy.calledOnce ).to.be.true;
62+
63+
const data = spy.args[ 0 ][ 1 ];
64+
expect( data.domTarget ).to.equal( document.body );
65+
} );
66+
} );
67+
68+
describe( 'handle isComposing property of the document', () => {
69+
let domMain;
70+
71+
beforeEach( () => {
72+
domMain = document.createElement( 'div' );
73+
} );
74+
75+
it( 'should set isComposing to true on compositionstart', () => {
76+
observer.onDomEvent( { type: 'compositionstart', target: domMain } );
77+
78+
expect( viewDocument.isComposing ).to.equal( true );
79+
} );
80+
81+
it( 'should set isComposing to false on compositionend', () => {
82+
observer.onDomEvent( { type: 'compositionstart', target: domMain } );
83+
84+
expect( viewDocument.isComposing ).to.equal( true );
85+
86+
observer.onDomEvent( { type: 'compositionend', target: domMain } );
87+
88+
expect( viewDocument.isComposing ).to.equal( false );
89+
} );
90+
91+
it( 'should not change isComposing on compositionupdate during composition', () => {
92+
observer.onDomEvent( { type: 'compositionstart', target: domMain } );
93+
94+
expect( viewDocument.isComposing ).to.equal( true );
95+
96+
observer.onDomEvent( { type: 'compositionupdate', target: domMain } );
97+
98+
expect( viewDocument.isComposing ).to.equal( true );
99+
} );
100+
101+
it( 'should not change isComposing on compositionupdate outside composition', () => {
102+
expect( viewDocument.isComposing ).to.equal( false );
103+
104+
observer.onDomEvent( { type: 'compositionupdate', target: domMain } );
105+
106+
expect( viewDocument.isComposing ).to.equal( false );
107+
} );
108+
} );
109+
} );

tests/view/view/view.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import KeyObserver from '../../../src/view/observer/keyobserver';
77
import FakeSelectionObserver from '../../../src/view/observer/fakeselectionobserver';
88
import SelectionObserver from '../../../src/view/observer/selectionobserver';
99
import FocusObserver from '../../../src/view/observer/focusobserver';
10+
import CompositionObserver from '../../../src/view/observer/compositionobserver';
1011
import createViewRoot from '../_utils/createroot';
1112
import Observer from '../../../src/view/observer/observer';
1213
import log from '@ckeditor/ckeditor5-utils/src/log';
@@ -21,7 +22,7 @@ import createElement from '@ckeditor/ckeditor5-utils/src/dom/createelement';
2122
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
2223

2324
describe( 'view', () => {
24-
const DEFAULT_OBSERVERS_COUNT = 5;
25+
const DEFAULT_OBSERVERS_COUNT = 6;
2526
let domRoot, view, viewDocument, ObserverMock, instantiated, enabled, ObserverMockGlobalCount;
2627

2728
testUtils.createSinonSandbox();
@@ -76,6 +77,7 @@ describe( 'view', () => {
7677
expect( view.getObserver( FocusObserver ) ).to.be.instanceof( FocusObserver );
7778
expect( view.getObserver( KeyObserver ) ).to.be.instanceof( KeyObserver );
7879
expect( view.getObserver( FakeSelectionObserver ) ).to.be.instanceof( FakeSelectionObserver );
80+
expect( view.getObserver( CompositionObserver ) ).to.be.instanceof( CompositionObserver );
7981
} );
8082

8183
describe( 'attachDomRoot()', () => {

0 commit comments

Comments
 (0)