Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop matching upon mismatch, reset when trigger char or backspace is pressed, take 2 #30687

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 95 additions & 56 deletions packages/components/src/autocomplete/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ import {
useLayoutEffect,
useState,
} from '@wordpress/element';
import { ENTER, ESCAPE, UP, DOWN, LEFT, RIGHT } from '@wordpress/keycodes';
import {
ENTER,
ESCAPE,
UP,
DOWN,
LEFT,
RIGHT,
BACKSPACE,
} from '@wordpress/keycodes';
import { __, _n, sprintf } from '@wordpress/i18n';
import { useInstanceId } from '@wordpress/compose';
import {
Expand Down Expand Up @@ -293,6 +301,85 @@ function Autocomplete( {
const [ filterValue, setFilterValue ] = useState( '' );
const [ autocompleter, setAutocompleter ] = useState( null );
const [ AutocompleterUI, setAutocompleterUI ] = useState( null );
const [ backspacing, setBackspacing ] = useState( false );

let textContent, text;
if ( isCollapsed( record ) ) {
textContent = getTextContent( slice( record, 0 ) );
text = deburr( textContent );
}

function getQuery( triggerPrefix ) {
const safeTrigger = escapeRegExp( triggerPrefix );
const match = text
.slice( text.lastIndexOf( triggerPrefix ) )
.match( new RegExp( `${ safeTrigger }([\u0000-\uFFFF]*)$` ) );
const query = match && match[ 1 ];
console.log( 'query: ', query );
return query;
}

function getCompleterFromTextContext() {
const textAfterSelection = getTextContent(
slice( record, undefined, getTextContent( record ).length )
);

return find( completers, ( { triggerPrefix, allowContext } ) => {
const index = text.lastIndexOf( triggerPrefix );

if ( index === -1 ) {
return false;
}

const textWithoutTrigger = text.slice(
index + triggerPrefix.length
);

console.log( 'filteredOptions: ', filteredOptions );
// If we don't have any matching filteredOptions
// we didn't have a new trigger typed, then we should not continue with this effect.
const mismatch = filteredOptions.length === 0;
const atTrigger = text.slice( -1 ) === triggerPrefix;
const tooDistantFromTrigger = textWithoutTrigger.length > 60; // 60 chars seem to be a good limit

console.log( `atTrigger: `, atTrigger );
console.log( 'mismatch: ', mismatch );
console.log( 'tooDistant: ', tooDistantFromTrigger );

if ( tooDistantFromTrigger ) return false;
if ( mismatch && ! atTrigger && ! backspacing ) {
console.log( 'here lies a mismatch!' );
return false;
}

if (
allowContext &&
! allowContext( text.slice( 0, index ), textAfterSelection )
) {
return false;
}

if (
/^\s/.test( textWithoutTrigger ) ||
/\s\s+$/.test( textWithoutTrigger )
) {
return false;
}

return /[\u0000-\uFFFF]*$/.test( textWithoutTrigger );
} );
}

function setupAutocompleter( completer ) {
setFilterValue( getQuery( completer.triggerPrefix ) );
setAutocompleter( completer );
setAutocompleterUI( () =>
completer !== autocompleter
? getAutoCompleterUI( completer )
: AutocompleterUI
);
setBackspacing( false );
}

function insertCompletion( replacement ) {
const end = record.start;
Expand Down Expand Up @@ -375,12 +462,16 @@ function Autocomplete( {
}

function handleKeyDown( event ) {
if ( event.keyCode === BACKSPACE ) {
setBackspacing( true );
}
if ( ! autocompleter ) {
return;
}
if ( filteredOptions.length === 0 ) {
return;
}

switch ( event.keyCode ) {
case UP:
setSelectedIndex(
Expand All @@ -404,12 +495,10 @@ function Autocomplete( {
case ENTER:
select( filteredOptions[ selectedIndex ] );
break;

case LEFT:
case RIGHT:
reset();
return;

default:
return;
}
Expand All @@ -420,70 +509,20 @@ function Autocomplete( {
event.stopPropagation();
}

let textContent;

if ( isCollapsed( record ) ) {
textContent = getTextContent( slice( record, 0 ) );
}

useEffect( () => {
if ( ! textContent ) {
return;
}

const text = deburr( textContent );
const textAfterSelection = getTextContent(
slice( record, undefined, getTextContent( record ).length )
);
const completer = find(
completers,
( { triggerPrefix, allowContext } ) => {
const index = text.lastIndexOf( triggerPrefix );

if ( index === -1 ) {
return false;
}

if (
allowContext &&
! allowContext( text.slice( 0, index ), textAfterSelection )
) {
return false;
}

const textWithoutTrigger = text.slice(
index + triggerPrefix.length
);

if (
/^\s/.test( textWithoutTrigger ) ||
/\s\s+$/.test( textWithoutTrigger )
) {
return false;
}

return /[\u0000-\uFFFF]*$/.test( textWithoutTrigger );
}
);

const completer = getCompleterFromTextContext();
if ( ! completer ) {
reset();
return;
}

const safeTrigger = escapeRegExp( completer.triggerPrefix );
const match = text
.slice( text.lastIndexOf( completer.triggerPrefix ) )
.match( new RegExp( `${ safeTrigger }([\u0000-\uFFFF]*)$` ) );
const query = match && match[ 1 ];
console.log( getQuery( completer.triggerPrefix ) ); // uncomment this to make this easier to test

setAutocompleter( completer );
setAutocompleterUI( () =>
completer !== autocompleter
? getAutoCompleterUI( completer )
: AutocompleterUI
);
setFilterValue( query );
setupAutocompleter( completer );
}, [ textContent ] );

const { key: selectedKey = '' } = filteredOptions[ selectedIndex ] || {};
Expand Down