@@ -34,6 +34,9 @@ export class Editor extends Component<EditorProps> {
3434 editorScript = "widgets/ckeditor/ckeditor.js" ;
3535 element : HTMLElement ;
3636 lastSentValue : string | undefined ;
37+ applyChangesDebounce : ( ) => void ;
38+ cancelRAF : ( ( ) => void ) | undefined ;
39+ hasFocus : boolean ;
3740
3841 constructor ( props : EditorProps ) {
3942 super ( props ) ;
@@ -42,10 +45,12 @@ export class Editor extends Component<EditorProps> {
4245 this . element = this . props . element ;
4346 this . editorKey = this . getNewKey ( ) ;
4447 this . editorHookProps = this . getNewEditorHookProps ( ) ;
45- this . onChange = debounce ( this . onChange . bind ( this ) , 500 ) ;
48+ this . onChange = this . onChange . bind ( this ) ;
49+ this . applyChangesDebounce = debounce ( this . applyChangesImmediately . bind ( this ) , 500 ) ;
4650 this . onKeyPress = this . onKeyPress . bind ( this ) ;
4751 this . onPasteContent = this . onPasteContent . bind ( this ) ;
4852 this . onDropContent = this . onDropContent . bind ( this ) ;
53+ this . hasFocus = false ;
4954 }
5055
5156 setNewRenderProps ( ) : void {
@@ -171,17 +176,21 @@ export class Editor extends Component<EditorProps> {
171176 }
172177 }
173178
174- // onChange is wrapped in debounce, so, we always need to check
175- // weather we sill have editor.
176179 onChange ( _event : CKEditorEventPayload < "change" > ) : void {
177180 if ( this . editor ) {
178181 const editorData = this . editor . getData ( ) ;
179182 const content = this . widgetProps . sanitizeContent ? DOMPurify . sanitize ( editorData ) : editorData ;
180183 this . lastSentValue = content ;
181- this . widgetProps . stringAttribute . setValue ( content ) ;
184+ this . applyChangesDebounce ( ) ;
182185 }
186+ }
183187
184- this . widgetProps . onChange ?. execute ( ) ;
188+ applyChangesImmediately ( ) {
189+ // put last seen content to the attribute if it exists
190+ if ( this . lastSentValue !== undefined ) {
191+ this . widgetProps . stringAttribute . setValue ( this . lastSentValue ) ;
192+ this . widgetProps . onChange ?. execute ( ) ;
193+ }
185194 }
186195
187196 addListeners ( ) : void {
@@ -253,6 +262,25 @@ export class Editor extends Component<EditorProps> {
253262 this . lastSentValue = undefined ;
254263 }
255264
265+ componentDidMount ( ) {
266+ this . cancelRAF = animationLoop ( ( ) => {
267+ if ( this . element && this . element . parentElement ) {
268+ const newHasFocus = this . element . parentElement . contains ( document . activeElement ) ;
269+ if ( newHasFocus !== this . hasFocus ) {
270+ this . hasFocus = newHasFocus ;
271+ if ( ! this . hasFocus ) {
272+ // changed from true to false, user left the element, apply changes immediately
273+ this . applyChangesImmediately ( ) ;
274+ }
275+ }
276+ }
277+ } ) ;
278+ }
279+
280+ componentWillUnmount ( ) {
281+ this . cancelRAF ?.( ) ;
282+ }
283+
256284 componentDidUpdate ( ) : void {
257285 const prevAttr = this . widgetProps . stringAttribute ;
258286 const nextAttr = this . props . widgetProps . stringAttribute ;
@@ -269,3 +297,18 @@ export class Editor extends Component<EditorProps> {
269297 return < MainEditor key = { key } config = { config } /> ;
270298 }
271299}
300+
301+ function animationLoop ( callback : ( ) => void ) : ( ) => void {
302+ let requestId : number ;
303+
304+ const requestFrame = ( ) => {
305+ requestId = window . requestAnimationFrame ( ( ) => {
306+ callback ( ) ;
307+ requestFrame ( ) ;
308+ } ) ;
309+ } ;
310+
311+ requestFrame ( ) ;
312+
313+ return ( ) => window . cancelAnimationFrame ( requestId ) ;
314+ }
0 commit comments