react-quill icon indicating copy to clipboard operation
react-quill copied to clipboard

Paste something will trigger the blur event.

Open BowlingX opened this issue 7 years ago • 21 comments

Hi, If you paste something, the onBlur event is triggered immediately. It will then not fire after you clicked outside.

Example: https://codepen.io/anon/pen/qPVQGK?editors=0010#0

  1. Paste something
  2. See how the alert shows up onBlur
  3. click outside (expect the alert box to show, will not show)

React-Quill version

  • [ ] master
  • [ ] 0.4.1
  • [ ] 1.0.0-beta-1
  • [ ] 1.0.0-beta-2
  • [ ] 1.0.0-beta-3
  • [ ] 1.0.0-beta-4
  • [ ] 1.0.0-beta-5
  • [ ] 1.0.0-rc-1
  • [ ] 1.0.0-rc-2
  • [ ] 1.0.0-rc-3
  • [ ] 1.0.0
  • [x] 1.1.0
  • [ ] Other (fork)

BowlingX avatar Oct 04 '17 19:10 BowlingX

@BowlingX @alexkrolick Hi, what is the proposed solution for this?

paulupendo avatar Oct 24 '17 07:10 paulupendo

I'm looking into this now, it seems when you paste a value into the editor, the range is set to null, rather than a new Range...and onEditorChangeSelection is not called again

chops318 avatar Oct 31 '17 16:10 chops318

So I narrowed it down to the clipboard's onPaste event in Quill.js, this line specifically

this.quill.updateContents(delta, Quill.sources.USER); After this is called, the editor loses focus and the onBlur event is called in react-quill. I'm not really sure how you could fix this behavior without maybe adding an onPaste event to react-quill. Any maintainers have any thought?

chops318 avatar Nov 01 '17 18:11 chops318

cc @jhchen (Quill maintainer)

  • http://quilljs.com/docs/api/#selection-change
  • https://github.com/quilljs/quill/issues/1680

Detecting null selection-change events is the recommended way to detect focus changes in Quill and this is the heuristic React-Quill uses to call the onBlur prop. It sounds like this causes some false positives with paste and possibly other events.

alexkrolick avatar Nov 01 '17 20:11 alexkrolick

I met the same problem when i paste something into editor, and trigger onBlur event immediately.

This is my solution:

<ReactQuill
  ref="editor"
  onBlur={this.handleQuillBlur}
  ...
/>
handleQuillBlur = (range, source, editor) => {
  setTimeout(() => {
    let fixRange = editor.getSelection();
    if (fixRange) {
      // get the editor instance.
      let editorInstance = this.refs.editor.getEditor();
      editorInstance.setContents(editorInstance.getContents());
      editorInstance.setSelection(fixRange);
      //editorInstance.focus();
    }
    // it's true blur.
    else {
      // do something.
    }
  }, 100); // 100ms is random...
}

luofei2011 avatar Dec 26 '17 11:12 luofei2011

Hi @alexkrolick, did you have the chance to invest some time into this issue or get some feedback from @jhchen? I am currently struggling with this issue as well. @luofei2011's solution is worth a try, but feels more like a workaround than an actual fix. Maybe you have some news for me/us.

Thanks for your help and time. :)

Copy & Paste onBlur and onFocus issue

@luofei2011: My problem is the following (and it does not work with your solution ... :/ ): We want to hide the toolbar (via css, so no toolbar=false issue) when the editor lost it's focus. And only show it again when the Editor is focused again.

But the following happens. When the user enters quill (onFocus) he can start typing. Then he copy & pastes something (for some reason onBlur is then triggered). I can still copy & paste but the toolbar does not appear again. Only when I type again.

===> Check out the examples below for a live demo.

Main Research

But it should work, because here is a working solution from quill (not react-quill's implementation): https://codepen.io/DmitrySkripkin/pen/eeXpZB?editors=0010#0. I found it here, it was also mentioned by @alexkrolick previously.

I am not entirely sure, but maybe the issue is here somewhere. Maybe @zenoamaro can help us out here?

Some other links

  • ~Next thing I try is onPaste. I will keep you posted here in this comment.~ ==> Update: did not work, as there is no such function.
  • ~Btw I also stumbled upon this comment and where they are talking about an issue related to an React 16.~ ==> Update: I was not able to get the same result/issue. See my working 16.2 example below.
  • It looks like quill has some onBlur issues in general, eg: "Clicking the quill toolbar is detected as blur event"

Examples

I have created some examples here:

  • React 15.6: https://codepen.io/natterstefan/pen/YLpyYZ?editors=0010
  • React 16.2: https://codepen.io/natterstefan/pen/ELNKKR?editors=0010

screen capture on 2018-04-27 at 10-02-12

natterstefan avatar Apr 26 '18 12:04 natterstefan

this.handleChange = this.handleChange.bind(null) This will solve you this issue.

vinay72 avatar Sep 19 '18 22:09 vinay72

@natterstefan maybe you used wrong way, https://codepen.io/anon/pen/zJeEXV?editors=0010 is ok. @vinay72 .bind(null) changed the this to null, calling this.setState will trigger error.

luofei2011 avatar Sep 20 '18 13:09 luofei2011

Okkay I got it .

vinay72 avatar Oct 12 '18 13:10 vinay72

You can try storing a ref to the ReactQuill component and focus()ing it after the change. Looks like it prevents de-focusing after pasting (this.editor is a ref):

handleContentChange = (content) => {
   // do something with new content
   this.editor.current.focus();
}

Of course, this may not work for cases where you need blur() with content change.

kadamska avatar Jan 25 '19 10:01 kadamska

@natterstefan were you able to fix the paste blur issue?

shaayaansayed avatar Feb 20 '19 04:02 shaayaansayed

Hi @shaayaansayed, to be honest, we decided to use a different approach in the UI, which lead to not touching this issue again. But, as far as I can see from the previous comments this one looks promising: https://github.com/zenoamaro/react-quill/issues/276#issuecomment-423184803.

natterstefan avatar Feb 20 '19 18:02 natterstefan

following @luofei2011 example this works for me :

onBlur={(range, source, editor) => {
              setTimeout(() => {
                let fixRange = editor.getSelection()
                if (fixRange) {
                  // paste event or none real blur event
                  console.log('fake blur')
                } else {
                  console.log('real blur')
                }
              }, 50) // random time
            }}

luke-robertson avatar Aug 02 '19 04:08 luke-robertson

Has anyone considered attempting to re-focus after paste (command/ctrl + V)? It looks like the first paste is perfectly fine - the textarea blurs on second paste. I'm cautious that this could lead to performance throttling. Thoughts?

TheSohaibAhmed avatar Jul 14 '20 04:07 TheSohaibAhmed

Has anyone considered attempting to re-focus after paste (command/ctrl + V)? It looks like the first paste is perfectly fine - the textarea blurs on second paste. I'm cautious that this could lead to performance throttling. Thoughts?

I have implemented the re-focus approach. I haven't noticed any slowdowns 🙌

PatrickDesign avatar Aug 18 '20 22:08 PatrickDesign

I am also facing this issue. The re-focus approach does work but it is a workaround and not a proper solution.

Can anyone point in the right direction? I would be happy to create a pull request for the fix.

mkamalkayani avatar Jun 14 '21 12:06 mkamalkayani

how is the refocus done ? @PatrickDesign

Joshuajrodrigues avatar Sep 22 '21 12:09 Joshuajrodrigues

I had the same problem. I added blur and paste listener to the editor

var editor = new Quill('#element_id' , { yourOptions });

 
editor.root.addEventListener('blur', function () {
   //action blur event
   console.log('blur event');
});

editor.root.addEventListener('paste', function(event) {
    //action paste event
    console.log('paste event');
});

When I tested that, I saw that the blur event is triggered before the paste event. To change that just add a setTimeout even only with 2ms. So you can handle your Front-end with the Framework of your choice.

var pasteAction = false;
editor.root.addEventListener('blur', function () {
	setTimeout(function() {
		if(  pasteAction == false  ){	
		    // do your stuff			
		}
		pasteAction = false;
	}, 2);
	
});

editor.root.addEventListener('paste', function(event) {
	console.log('paste event!');
	//set a variable that paste happend
	pasteAction = true;
});

I hope that can help someone. I removed the FW related code with pseudo snippets (pasteAction)

gekko2588 avatar Oct 14 '21 05:10 gekko2588

  • Hello I have similar problem where I have onChange and onBlur and in the both case I was submitting. I want to submit in that cases but I did not want to submit when the user copy-paste something.
  • I solved my problem with:

const handleBlur = (event, source) =>{

/* If the user uses copy and paste second parameter "source" return a string 'silent' in the blur event listener, and when the user dispatch event, clicking outside the input element source returns a string "user" where you can do simple if the case. This is the case when you want to submit on blur, but you want to disable auto submitting when the user does copy-paste. */

if(source === 'silent') return;

//Submit When User Clicks }

This is the case in React, I really hope this will help someone. :)

aleksandarLazic1998 avatar Oct 17 '21 09:10 aleksandarLazic1998

Perfect solution via CSS :focus-within with content property and resize observer

The setTimeout solutions work but can introduce other timing/ UX issues. I found a solution that works perfectly using the fact that CSS ::focus-within correctly captures whether quill is focused or not (regardless of the clipboard blur issue). I have built the solution via an Angular directive but it can easily be changed to fit any framework you are using. Just wanted to share my solution in case other people need a clean solution for this problem ❤️

I know this is the react-quill repository so Angular code seems to not be useful here - this is just my reference implementation as a quick way to share my code, I am not a React dev so I cannot translate the code unfortunately.

import { Directive, ElementRef, EventEmitter, OnDestroy, Output } from '@angular/core';
import { before, focusWithin } from '@t5s/client/ui/style/common';
import { tss } from '@t5s/client/util/tss';

const hostElClass = tss({
  '.quill-focus-checker': {
    ...before({
      content: '""',
    }),
  },
  ...focusWithin({
    '.quill-focus-checker': {
      ...before({
        content: '"FOCUS"',
      }),
    },
  }),
});

/**
 * It is very difficult to find out whether quill is focused or not, because pasting content causes the quill editor to loose focus.
 * The clipboard needs focus for a split second before re-focusing the editor. We use the fact that the css property ::focus-within does not
 * seem to be affected by this so we register a resize observer with focus-within:content to detect real focus changes
 */
@Directive({
  selector: '[t5sQuillFocusChange]',
  exportAs: 't5sQuillFocusChange',
})
export class QuillFocusChangeDirective implements OnDestroy {

  private resizeObserver?: ResizeObserver = new ResizeObserver(() => {
    const resizeEl = this.resizeEl;
    if (!resizeEl) return;
    const content = getComputedStyle(resizeEl, '::before').getPropertyValue('content');
    const focused = content.includes('FOCUS');
    this.focusChange.emit({ focused });
  });

  private resizeEl?: HTMLDivElement;

  constructor(elRef: ElementRef<HTMLElement>) {
    elRef.nativeElement.classList.add(hostElClass);

    this.resizeEl = document.createElement('div');
    this.resizeEl.classList.add('quill-focus-checker');
    this.resizeEl.style.setProperty('visibility', 'hidden');
    this.resizeEl.style.setProperty('pointer-events', 'none');
    this.resizeEl.style.setProperty('opacity', '0');
    this.resizeEl.style.setProperty('height', '0px');
    this.resizeEl.style.setProperty('width', 'fit-content');
    elRef.nativeElement.appendChild(this.resizeEl);
    this.resizeObserver.observe(this.resizeEl);
  }

  ngOnDestroy() {
    this.resizeObserver?.unobserve(this.resizeEl);
    this.resizeObserver?.disconnect();
    this.resizeObserver = undefined;
    this.resizeEl = undefined;
  }

  @Output('t5sQuillFocusChange') focusChange = new EventEmitter<{ focused: boolean }>();
}

derbenoo avatar Aug 18 '22 08:08 derbenoo

I've found a solution. The FocusEvent has relativeTarget property, which may refer to .ql-clipboard element. So, all you need to do is

  function handleBlur(event: FocusEvent) {
    if (
      event.relatedTarget instanceof HTMLElement &&
      event.relatedTarget.classList.contains("ql-clipboard")
    ) {
      return;
    }
    // ...your code
  }

This checks if the current blur event is related with copy/paste event.

suspense-dev avatar Dec 25 '23 14:12 suspense-dev