Skip to content

Commit

Permalink
Rich text: only selectively handle keyup/pointerup (#48385)
Browse files Browse the repository at this point in the history
  • Loading branch information
ellatrix authored Feb 24, 2023
1 parent 8164936 commit bf2efa5
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 25 deletions.
2 changes: 2 additions & 0 deletions packages/rich-text/src/component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useCopyHandler } from './use-copy-handler';
import { useFormatBoundaries } from './use-format-boundaries';
import { useSelectObject } from './use-select-object';
import { useInputAndSelection } from './use-input-and-selection';
import { useSelectionChangeCompat } from './use-selection-change-compat';
import { useDelete } from './use-delete';

export function useRichText( {
Expand Down Expand Up @@ -240,6 +241,7 @@ export function useRichText( {
isSelected,
onSelectionChange,
} ),
useSelectionChangeCompat(),
useRefEffect( () => {
applyFromProps();
didMount.current = true;
Expand Down
29 changes: 4 additions & 25 deletions packages/rich-text/src/component/use-input-and-selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,11 @@ export function useInputAndSelection( props ) {

/**
* Syncs the selection to local state. A callback for the
* `selectionchange`, `keyup`, `mouseup` and `touchend` events.
*
* @param {Event} event
* `selectionchange` event.
*/
function handleSelectionChange( event ) {
const {
record,
applyRecord,
createRecord,
isSelected,
onSelectionChange,
} = propsRef.current;
function handleSelectionChange() {
const { record, applyRecord, createRecord, onSelectionChange } =
propsRef.current;

// Check if the implementor disabled editing. `contentEditable`
// does disable input, but not text selection, so we must ignore
Expand Down Expand Up @@ -178,10 +171,6 @@ export function useInputAndSelection( props ) {
return;
}

if ( event.type !== 'selectionchange' && ! isSelected ) {
return;
}

// In case of a keyboard event, ignore selection changes during
// composition.
if ( isComposing ) {
Expand Down Expand Up @@ -295,13 +284,6 @@ export function useInputAndSelection( props ) {
element.addEventListener( 'compositionstart', onCompositionStart );
element.addEventListener( 'compositionend', onCompositionEnd );
element.addEventListener( 'focus', onFocus );
// Selection updates must be done at these events as they
// happen before the `selectionchange` event. In some cases,
// the `selectionchange` event may not even fire, for
// example when the window receives focus again on click.
element.addEventListener( 'keyup', handleSelectionChange );
element.addEventListener( 'mouseup', handleSelectionChange );
element.addEventListener( 'touchend', handleSelectionChange );
ownerDocument.addEventListener(
'selectionchange',
handleSelectionChange
Expand All @@ -314,9 +296,6 @@ export function useInputAndSelection( props ) {
);
element.removeEventListener( 'compositionend', onCompositionEnd );
element.removeEventListener( 'focus', onFocus );
element.removeEventListener( 'keyup', handleSelectionChange );
element.removeEventListener( 'mouseup', handleSelectionChange );
element.removeEventListener( 'touchend', handleSelectionChange );
ownerDocument.removeEventListener(
'selectionchange',
handleSelectionChange
Expand Down
59 changes: 59 additions & 0 deletions packages/rich-text/src/component/use-selection-change-compat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* WordPress dependencies
*/
import { useRefEffect } from '@wordpress/compose';

/**
* Sometimes some browsers are not firing a `selectionchange` event when
* changing the selection by mouse or keyboard. This hook makes sure that, if we
* detect no `selectionchange` or `input` event between the up and down events,
* we fire a `selectionchange` event.
*
* @return {import('@wordpress/compose').RefEffect} A ref effect attaching the
* listeners.
*/
export function useSelectionChangeCompat() {
return useRefEffect( ( element ) => {
const { ownerDocument } = element;
const { defaultView } = ownerDocument;
const selection = defaultView.getSelection();

let range;

function getRange() {
return selection.rangeCount ? selection.getRangeAt( 0 ) : null;
}

function onDown( event ) {
const type = event.type === 'keydown' ? 'keyup' : 'pointerup';

function onCancel() {
ownerDocument.removeEventListener( type, onUp );
ownerDocument.removeEventListener(
'selectionchange',
onCancel
);
ownerDocument.removeEventListener( 'input', onCancel );
}

function onUp() {
onCancel();
if ( range === getRange() ) return;
ownerDocument.dispatchEvent( new Event( 'selectionchange' ) );
}

ownerDocument.addEventListener( type, onUp );
ownerDocument.addEventListener( 'selectionchange', onCancel );
ownerDocument.addEventListener( 'input', onCancel );

range = getRange();
}

element.addEventListener( 'pointerdown', onDown );
element.addEventListener( 'keydown', onDown );
return () => {
element.removeEventListener( 'pointerdown', onDown );
element.removeEventListener( 'keydown', onDown );
};
}, [] );
}

1 comment on commit bf2efa5

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in bf2efa5.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/4263620066
📝 Reported issues:

Please sign in to comment.