Skip to content

Commit

Permalink
Rich text: move Autocomplete and Enter key handler to ref callback (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
ellatrix authored May 13, 2021
1 parent d040a16 commit 4c09aff
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 88 deletions.
84 changes: 17 additions & 67 deletions packages/block-editor/src/components/rich-text/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,19 @@ import { omit } from 'lodash';
*/
import { RawHTML, useRef, useCallback, forwardRef } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import {
children as childrenSource,
getBlockTransforms,
findTransform,
} from '@wordpress/blocks';
import { children as childrenSource } from '@wordpress/blocks';
import { useInstanceId, useMergeRefs } from '@wordpress/compose';
import {
__unstableUseRichText as useRichText,
__unstableCreateElement,
isEmpty,
__unstableIsEmptyLine as isEmptyLine,
insert,
__unstableInsertLineSeparator as insertLineSeparator,
split,
toHTMLString,
isCollapsed,
removeFormat,
} from '@wordpress/rich-text';
import deprecated from '@wordpress/deprecated';
import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes';
import { BACKSPACE, DELETE } from '@wordpress/keycodes';

/**
* Internal dependencies
Expand All @@ -43,6 +36,7 @@ import { useCaretInFormat } from './use-caret-in-format';
import { useMarkPersistent } from './use-mark-persistent';
import { usePasteHandler } from './use-paste-handler';
import { useInputRules } from './use-input-rules';
import { useEnter } from './use-enter';
import { useFormatTypes } from './use-format-types';
import FormatEdit from './format-edit';
import { getMultilineTag, getAllowedFormats } from './utils';
Expand Down Expand Up @@ -119,9 +113,7 @@ function RichTextWrapper(
const { selectionStart, selectionEnd, isSelected, disabled } = useSelect(
selector
);
const { selectionChange, __unstableMarkAutomaticChange } = useDispatch(
blockEditorStore
);
const { selectionChange } = useDispatch( blockEditorStore );
const multilineTag = getMultilineTag( multiline );
const adjustedAllowedFormats = getAllowedFormats( {
allowedFormats,
Expand Down Expand Up @@ -313,57 +305,7 @@ function RichTextWrapper(
return;
}

if ( event.keyCode === ENTER ) {
event.preventDefault();

const _value = { ...value };
_value.formats = removeEditorOnlyFormats( value );
const canSplit = onReplace && onSplit;

if ( onReplace ) {
const transforms = getBlockTransforms( 'from' ).filter(
( { type } ) => type === 'enter'
);
const transformation = findTransform( transforms, ( item ) => {
return item.regExp.test( _value.text );
} );

if ( transformation ) {
onReplace( [
transformation.transform( {
content: _value.text,
} ),
] );
__unstableMarkAutomaticChange();
}
}

if ( multiline ) {
if ( event.shiftKey ) {
if ( ! disableLineBreaks ) {
onChange( insert( _value, '\n' ) );
}
} else if ( canSplit && isEmptyLine( _value ) ) {
splitValue( _value );
} else {
onChange( insertLineSeparator( _value ) );
}
} else {
const { text, start, end } = _value;
const canSplitAtEnd =
onSplitAtEnd && start === end && end === text.length;

if ( event.shiftKey || ( ! canSplit && ! canSplitAtEnd ) ) {
if ( ! disableLineBreaks ) {
onChange( insert( _value, '\n' ) );
}
} else if ( ! canSplit && canSplitAtEnd ) {
onSplitAtEnd();
} else if ( canSplit ) {
splitValue( _value );
}
}
} else if ( keyCode === DELETE || keyCode === BACKSPACE ) {
if ( keyCode === DELETE || keyCode === BACKSPACE ) {
const { start, end, text } = value;
const isReverse = keyCode === BACKSPACE;
const hasActiveFormats =
Expand Down Expand Up @@ -450,6 +392,17 @@ function RichTextWrapper(
preserveWhiteSpace,
pastePlainText,
} ),
useEnter( {
removeEditorOnlyFormats,
value,
onReplace,
onSplit,
multiline,
onChange,
disableLineBreaks,
splitValue,
onSplitAtEnd,
} ),
anchorRef,
forwardedRef,
] ) }
Expand All @@ -462,10 +415,7 @@ function RichTextWrapper(
'rich-text'
) }
onFocus={ unstableOnFocus }
onKeyDown={ ( event ) => {
autocompleteProps.onKeyDown( event );
onKeyDown( event );
} }
onKeyDown={ onKeyDown }
/>
</>
);
Expand Down
105 changes: 105 additions & 0 deletions packages/block-editor/src/components/rich-text/use-enter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* WordPress dependencies
*/
/**
* WordPress dependencies
*/
import { useRef } from '@wordpress/element';
import { useRefEffect } from '@wordpress/compose';
import { ENTER } from '@wordpress/keycodes';
import {
insert,
__unstableIsEmptyLine as isEmptyLine,
__unstableInsertLineSeparator as insertLineSeparator,
} from '@wordpress/rich-text';
import { getBlockTransforms, findTransform } from '@wordpress/blocks';
import { useDispatch } from '@wordpress/data';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';

export function useEnter( props ) {
const { __unstableMarkAutomaticChange } = useDispatch( blockEditorStore );
const propsRef = useRef( props );
propsRef.current = props;
return useRefEffect( ( element ) => {
function onKeyDown( event ) {
if ( event.defaultPrevented ) {
return;
}

const {
removeEditorOnlyFormats,
value,
onReplace,
onSplit,
multiline,
onChange,
disableLineBreaks,
splitValue,
onSplitAtEnd,
} = propsRef.current;

if ( event.keyCode !== ENTER ) {
return;
}

event.preventDefault();

const _value = { ...value };
_value.formats = removeEditorOnlyFormats( value );
const canSplit = onReplace && onSplit;

if ( onReplace ) {
const transforms = getBlockTransforms( 'from' ).filter(
( { type } ) => type === 'enter'
);
const transformation = findTransform( transforms, ( item ) => {
return item.regExp.test( _value.text );
} );

if ( transformation ) {
onReplace( [
transformation.transform( {
content: _value.text,
} ),
] );
__unstableMarkAutomaticChange();
}
}

if ( multiline ) {
if ( event.shiftKey ) {
if ( ! disableLineBreaks ) {
onChange( insert( _value, '\n' ) );
}
} else if ( canSplit && isEmptyLine( _value ) ) {
splitValue( _value );
} else {
onChange( insertLineSeparator( _value ) );
}
} else {
const { text, start, end } = _value;
const canSplitAtEnd =
onSplitAtEnd && start === end && end === text.length;

if ( event.shiftKey || ( ! canSplit && ! canSplitAtEnd ) ) {
if ( ! disableLineBreaks ) {
onChange( insert( _value, '\n' ) );
}
} else if ( ! canSplit && canSplitAtEnd ) {
onSplitAtEnd();
} else if ( canSplit ) {
splitValue( _value );
}
}
}

element.addEventListener( 'keydown', onKeyDown );
return () => {
element.removeEventListener( 'keydown', onKeyDown );
};
}, [] );
}
24 changes: 20 additions & 4 deletions packages/components/src/autocomplete/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ import {
BACKSPACE,
} from '@wordpress/keycodes';
import { __, _n, sprintf } from '@wordpress/i18n';
import { useInstanceId, useDebounce } from '@wordpress/compose';
import {
useInstanceId,
useDebounce,
useMergeRefs,
useRefEffect,
} from '@wordpress/compose';
import {
create,
slice,
Expand Down Expand Up @@ -565,15 +570,26 @@ function useAutocomplete( {

export function useAutocompleteProps( options ) {
const ref = useRef();
const onKeyDownRef = useRef();
const { popover, listBoxId, activeId, onKeyDown } = useAutocomplete( {
...options,
contentRef: ref,
} );

onKeyDownRef.current = onKeyDown;
return {
ref,
ref: useMergeRefs( [
ref,
useRefEffect( ( element ) => {
function _onKeyDown( event ) {
onKeyDownRef.current( event );
}
element.addEventListener( 'keydown', _onKeyDown );
return () => {
element.removeEventListener( 'keydown', _onKeyDown );
};
}, [] ),
] ),
children: popover,
onKeyDown,
'aria-autocomplete': listBoxId ? 'list' : undefined,
'aria-owns': listBoxId,
'aria-activedescendant': activeId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ async function getSelectedFlatIndices() {
* Tests if the native selection matches the block selection.
*/
async function testNativeSelection() {
// Wait for the selection to update.
await page.evaluate( () => new Promise( window.requestAnimationFrame ) );
// Wait for the selection to update and async mode to update classes of
// deselected blocks.
await page.evaluate( () => new Promise( window.requestIdleCallback ) );
await page.evaluate( () => {
const selection = window.getSelection();
const elements = Array.from(
Expand Down
31 changes: 16 additions & 15 deletions packages/rich-text/src/component/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,20 @@ export function useRichText( {
record.current.end = selectionEnd;
}

const hadSelectionUpdate = useRef( false );

if ( ! record.current ) {
setRecordFromProps();
} else if (
selectionStart !== record.current.start ||
selectionEnd !== record.current.end
) {
hadSelectionUpdate.current = isSelected;
record.current = {
...record.current,
start: selectionStart,
end: selectionEnd,
};
}

/**
Expand Down Expand Up @@ -149,24 +161,13 @@ export function useRichText( {

// Value updates must happen synchonously to avoid overwriting newer values.
useLayoutEffect( () => {
if ( ! didMount.current ) {
if ( ! hadSelectionUpdate.current ) {
return;
}

if (
isSelected &&
( selectionStart !== record.current.start ||
selectionEnd !== record.current.end )
) {
applyFromProps();
} else {
record.current = {
...record.current,
start: selectionStart,
end: selectionEnd,
};
}
}, [ selectionStart, selectionEnd, isSelected ] );
applyFromProps();
hadSelectionUpdate.current = false;
}, [ hadSelectionUpdate.current ] );

function focus() {
ref.current.focus();
Expand Down

0 comments on commit 4c09aff

Please sign in to comment.